!84 update 0.9.8

Merge pull request !84 from Foming/dev
V0.9.8
Foming 3 years ago committed by Gitee
commit 274e87ca4b
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F

@ -176,7 +176,7 @@ sql文件的目录在report-core --> src --> main --> resources -- > db.migra
- nodeV16适配
- 增加省市区地图等图
- 增加基础边框样式
- Execl报表功能增加与bug修复
- Excel报表功能增加与bug修复
## 已知问题

@ -54,7 +54,7 @@ module.exports = {
{title: '数据集', path: '/guide/dataset'},
{title: '报表管理', path: '/guide/reportmanager'},
{title: '大屏报表', path: '/guide/dashboard'},
{title: '表格报表', path: '/guide/execl'},
{title: '表格报表', path: '/guide/excel'},
{title: '导入导出', path: '/guide/importexport'},
{title: '图表组件', path: '/guide/charts'},
]

@ -36,6 +36,7 @@
- 第二种在pom.xml中添加 <br>
使用build.sh脚本编译或者maven package编译都可以前端页面选择jdbc数据源填上对应驱动。<br>
**注意:** 底层只实现了mysql的解析如果你的数据库和mysql差异较大比如一些函数、关键字和mysql是不同的用法、含义那就需要重写底层解析代码(datasource) <br>
<br>
### 非JDBC驱动类数据源添加

@ -1,34 +1,34 @@
# 介绍
execl报表基于Luckysheet开发[Luckysheet](https://gitee.com/mengshukeji/Luckysheet) 一款纯前端类似excel的在线表格功能强大、配置简单、完全开源。
**注意:** execl报表目前只是简单集成如果你遇到了一些问题请在此[Issue](https://gitee.com/anji-plus/report/issues/I4CEWV) 下面进行回复。<br>
Excel报表基于Luckysheet开发[Luckysheet](https://gitee.com/mengshukeji/Luckysheet) 一款纯前端类似excel的在线表格功能强大、配置简单、完全开源。
**注意:** Excel报表目前只是简单集成如果你遇到了一些问题请在此[Issue](https://gitee.com/anji-plus/report/issues/I4CEWV) 下面进行回复。<br>
## 表格报表设计
进入表格设计方法1 <br>
从报表管理模块选择需要设计的大屏,按图示进入大屏设计界面 <br>
![img](../picture/execl/img.png) <br>
![img](../picture/excel/img.png) <br>
进入大屏设计方法2 <br>
从大屏报表模块选择需要设计的大屏,按图示进入大屏设计界面 <br>
![img2](../picture/execl/img_1.png) <br>
![img2](../picture/excel/img_1.png) <br>
## 简介
![img3](../picture/execl/img_2.png) <br>
![img3](../picture/excel/img_2.png) <br>
## 使用
**注**:不建议一列中同时存在俩个字段数据,同时一列值也建议不要存到超大数据量,肯定无法显示的<br>
![img4](../picture/execl/img_3.png) <br>
![img4](../picture/excel/img_3.png) <br>
## 预览/保存
点击保存,则会将数据写入到库中。<br>
点击预览,则进入预览界面。<br>
![img](../picture/execl/img_4.png) <br>
![img](../picture/excel/img_4.png) <br>
## 预览界面
可以进行导出操作。<br>
![img](../picture/execl/img_5.png) <br>
![img](../picture/excel/img_5.png) <br>

@ -37,9 +37,11 @@
<br>
- 页面提示“404” <br>
1、确保访问地址无误根据部署方式的不同9095/9528 端口皆可以进入项目,如果一个不行试另一个端口 <br>
1、确保访问地址无误根据部署方式的不同 9095/9528 端口皆可以进入项目,如果一个不行试另一个端口 <br>
2、确定前端是否启动 <br>
3、确定后端是否启动 br>
3、确定后端是否启动 <br>
4、如果你是前后端分离部署看看你前端config配置文件中的BASE_API有没有改成你后端的IP端口 <br>
5、使用nginx转发遇到问题也是同上 <br>
<br>
@ -103,7 +105,7 @@
<br>
- 文本框颜色无法改变 <br>
使用文本框,改变颜色,无法改变。已知有概率性的出现无法修改文本框颜色的情况,因为无法重现,暂时不能排查到是哪里问题。解决方法有以下 <br>
使用文本框,改变颜色,无法改变。已知有概率性的出现无法修改文本框颜色的情况。解决方法有以下 <br>
1、保存退出大屏重新进入大屏修改文本框颜色 <br>
2、删掉文本框重新拖动一个 <br>

@ -1,7 +1,7 @@
```
第一步下载zip包解压<br>
第二步conf->bootstrap.yml修改mysql连接<br>
第三步启动bin目录下start.sh<br>
第一步下载zip包解压
第二步conf->bootstrap.yml修改mysql连接
第三步启动bin目录下start.sh
第四步,访问 http://localhost:9095
```

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@ -333,6 +333,9 @@ public class ReportExcelServiceImpl implements ReportExcelService {
JSONObject addCell = cellDynamicData.get(j);
//字段
String fieldLabel = addCell.getString(dataSet.getFieldLabel());
if (StringUtils.isBlank(fieldLabel)) {
fieldLabel = StringUtils.EMPTY;
}
String replace = v.replace("#{".concat(dataSet.getSetCode()).concat(".").concat(dataSet.getFieldLabel()).concat("}"), fieldLabel);
//转字符串,解决深拷贝问题
JSONObject addCellData = JSONObject.parseObject(cellStr);

@ -66,4 +66,11 @@ public class ReportShareController extends GaeaBaseController<ReportShareParam,
return ResponseBean.builder().data(reportShareService.detailByCode(shareCode)).build();
}
@PostMapping({"/shareDelay"})
@Permission(code = "shareDelay", name = "分享延期")
public ResponseBean shareDelay(@RequestBody ReportShareDto dto) {
reportShareService.shareDelay(dto);
return ResponseBean.builder().build();
}
}

@ -2,6 +2,8 @@
package com.anjiplus.template.gaea.business.modules.reportshare.controller.dto;
import java.io.Serializable;
import com.anji.plus.gaea.annotation.Formatter;
import com.anji.plus.gaea.curd.dto.GaeaBaseDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -58,4 +60,6 @@ public class ReportShareDto extends GaeaBaseDTO implements Serializable {
private boolean sharePasswordFlag = false;
private String reportType;
}

@ -1,16 +1,30 @@
/**/
package com.anjiplus.template.gaea.business.modules.reportshare.controller.param;
import com.anji.plus.gaea.annotation.Query;
import com.anji.plus.gaea.constant.QueryEnum;
import com.anji.plus.gaea.curd.params.PageParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* @desc ReportShare
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
* @author Raod
* @desc ReportShare
* @date 2021-08-18 13:37:26.663
**/
@Data
public class ReportShareParam extends PageParam implements Serializable{
public class ReportShareParam extends PageParam implements Serializable {
/** 分享编码系统生成默认UUID */
@Query(value = QueryEnum.EQ)
private String shareCode;
/** 报表编码 */
@Query(value = QueryEnum.LIKE)
private String reportCode;
/** 分享有效期类型 */
@Query(value = QueryEnum.EQ)
private String shareValidType;
}

@ -40,11 +40,13 @@ public class ReportShare extends GaeaBaseEntity {
private Integer deleteFlag;
/** 分享码 */
@TableField(exist = false)
private String sharePassword;
@TableField(exist = false)
private boolean sharePasswordFlag;
/** 大屏类型 report excel */
@TableField(exist = false)
private String reportType;
}

@ -24,4 +24,10 @@ public interface ReportShareService extends GaeaBaseService<ReportShareParam, Re
ReportShareDto insertShare(ReportShareDto dto);
ReportShare detailByCode(String shareCode);
/**
*
* @param dto
*/
void shareDelay(ReportShareDto dto);
}

@ -29,7 +29,11 @@ import org.springframework.stereotype.Service;
**/
@Service
public class ReportShareServiceImpl implements ReportShareService {
private static final String SHARE_AJFLAG = "#/aj/";
private static final String SHARE_ELFLAG = "#/el/";
private static final String REPORT = "report_screen";
private static final String EXCEL = "report_excel";
/**
* aj
*/
@ -86,6 +90,23 @@ public class ReportShareServiceImpl implements ReportShareService {
return reportShare;
}
/**
*
*
* @param dto
*/
@Override
public void shareDelay(ReportShareDto dto) {
Integer shareValidType = dto.getShareValidType();
if (null == dto.getId() || null == shareValidType) {
throw BusinessExceptionBuilder.build("入参不完整");
}
ReportShare entity = selectOne(dto.getId());
entity.setShareValidTime(DateUtil.getFutureDateTmdHmsByTime(entity.getShareValidTime(), shareValidType));
entity.setShareToken(JwtUtil.createToken(entity.getReportCode(), entity.getShareCode(), entity.getSharePassword(), entity.getShareValidTime()));
update(entity);
}
@Override
public void processBeforeOperation(ReportShare entity, BaseOperationEnum operationEnum) throws BusinessException {
switch (operationEnum) {
@ -109,11 +130,31 @@ public class ReportShareServiceImpl implements ReportShareService {
//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);
// 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);
// }
if (REPORT.equals(entity.getReportType())) {
if (entity.getShareUrl().contains(SHARE_URL)) {
String prefix = entity.getShareUrl().substring(0, entity.getShareUrl().indexOf("#"));
entity.setShareUrl(prefix + SHARE_AJFLAG + shareCode);
}else {
entity.setShareUrl(entity.getShareUrl() + SHARE_AJFLAG + shareCode);
}
}else if (EXCEL.equals(entity.getReportType())) {
if (entity.getShareUrl().contains(SHARE_URL)) {
String prefix = entity.getShareUrl().substring(0, entity.getShareUrl().indexOf("#"));
entity.setShareUrl(prefix + SHARE_ELFLAG + shareCode);
}else {
entity.setShareUrl(entity.getShareUrl() + SHARE_ELFLAG + shareCode);
}
}else {
return;
}
entity.setShareValidTime(DateUtil.getFutureDateTmdHms(entity.getShareValidType()));

@ -62,6 +62,23 @@ public class DateUtil {
return calendar.getTime();
}
/**
*
* @param time
* @param day
* @return
*/
public static Date getFutureDateTmdHmsByTime(Date time, int day) {
if (day <= 0) {
//默认2099年
return parse("2099-01-01", defaultDatePattern);
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(time);
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,9 @@
-- 调整gaea_report_dashboard_widget部分字段长度
ALTER TABLE `aj_report`.`gaea_report_dashboard_widget` MODIFY COLUMN `setup` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '组件的渲染属性json' AFTER `type`;
ALTER TABLE `aj_report`.`gaea_report_dashboard_widget` MODIFY COLUMN `data` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '组件的数据属性json' AFTER `setup`;
ALTER TABLE `aj_report`.`gaea_report_dashboard_widget` MODIFY COLUMN `collapse` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '组件的配置属性json' AFTER `data`;
ALTER TABLE `aj_report`.`gaea_report_dashboard_widget` MODIFY COLUMN `position` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '组件的大小位置属性json' AFTER `collapse`;

@ -0,0 +1,10 @@
INSERT INTO `aj_report`.`access_authority`(`parent_target`, `target`, `target_name`, `action`, `action_name`, `sort`, `enable_flag`, `delete_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES ('report', 'reportShareManage', '报表分享', 'query', '查询报表分享', 231, 1, 0, 'admin', '2019-07-23 15:59:40', 'admin', '2019-07-23 15:59:40', 1);
INSERT INTO `aj_report`.`access_authority`(`parent_target`, `target`, `target_name`, `action`, `action_name`, `sort`, `enable_flag`, `delete_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES ('report', 'reportShareManage', '报表分享', 'detail', '查询明细', 232, 1, 0, 'admin', '2019-07-23 15:59:40', 'admin', '2019-07-23 15:59:40', 1);
INSERT INTO `aj_report`.`access_authority`(`parent_target`, `target`, `target_name`, `action`, `action_name`, `sort`, `enable_flag`, `delete_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES ('report', 'reportShareManage', '报表分享', 'shareDelay', '分享延期', 233, 1, 0, 'admin', '2019-07-23 15:59:40', 'admin', '2019-07-23 15:59:40', 1);
INSERT INTO `aj_report`.`access_authority`(`parent_target`, `target`, `target_name`, `action`, `action_name`, `sort`, `enable_flag`, `delete_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES ('report', 'reportShareManage', '报表分享', 'delete', '删除分享', 234, 1, 0, 'admin', '2019-07-23 15:59:40', 'admin', '2019-07-23 15:59:40', 1);
INSERT INTO `aj_report`.`access_role_authority`(`role_code`, `target`, `action`) VALUES ('root', 'reportShareManage', 'query');
INSERT INTO `aj_report`.`access_role_authority`(`role_code`, `target`, `action`) VALUES ('root', 'reportShareManage', 'detail');
INSERT INTO `aj_report`.`access_role_authority`(`role_code`, `target`, `action`) VALUES ('root', 'reportShareManage', 'shareDelay');
INSERT INTO `aj_report`.`access_role_authority`(`role_code`, `target`, `action`) VALUES ('root', 'reportShareManage', 'delete');
ALTER TABLE `aj_report`.`gaea_report_share` ADD COLUMN share_password varchar(10) DEFAULT NULL COMMENT '分享码' AFTER share_url;

@ -5,5 +5,5 @@ const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
BASE_API: '"http://127.0.0.1:9095"'
// BASE_API: '"http://10.108.26.197:9095"'
//BASE_API: '"http://10.108.26.197:9095"'
})

@ -16,12 +16,20 @@ export function reportShareAdd(data) {
})
}
export function reportShareDelay(data) {
return request({
url: 'reportShare/shareDelay',
method: 'post',
data
})
}
export function reportShareDeleteBatch(data) {
return request({
url: 'reportShare/delete/batch',
method: 'post',
data
})
return request({
url: 'reportShare/delete/batch',
method: 'post',
data
})
}
export function reportShareUpdate(data) {
@ -35,7 +43,7 @@ export function reportShareDetail(data) {
return request({
url: 'reportShare/' + data.id,
method: 'get',
params: { accessKey: data.accessKey }
params: {accessKey: data.accessKey}
})
}
@ -43,8 +51,8 @@ export function reportShareDetailByCode(data) {
return request({
url: 'reportShare/detailByCode',
method: 'get',
params: { shareCode: data }
params: {shareCode: data}
})
}
export default { reportShareList, reportShareAdd, reportShareDeleteBatch, reportShareUpdate, reportShareDetail }
export default {reportShareList, reportShareAdd, reportShareDeleteBatch, reportShareUpdate, reportShareDetail}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@ -3,27 +3,7 @@
height: 100%;
background: #242a30;
color: #fff;
.layout-bar {
height: 40px;
line-height: 40px;
font-size: 12px;
padding: 0 10px;
display: flex;
flex-direction: row;
overflow: hidden;
.bar-item {
margin-right: 20px;
cursor: pointer;
.iconfont {
font-size: 12px;
margin-right: 4px;
}
.el-dropdown-link {
color: #fff;
cursor: pointer;
}
}
}
.layout-container {
width: 100%;
height: calc(100vh - 40px);
@ -31,70 +11,6 @@
flex-direction: row;
justify-content: space-between;
overflow: hidden;
.layout-left {
width: 200px;
background: #242a30;
overflow-x: hidden;
overflow-y: auto;
.chart-type {
display: flex;
flex-direction: row;
overflow: hidden;
.type-left {
width: 100%;
height: calc(100vh - 80px);
text-align: center;
/deep/.el-tabs__header {
width: 30%;
margin-right: 0;
.el-tabs__nav-wrap {
&::after {
background: transparent;
}
.el-tabs__item {
text-align: center;
width: 100%;
color: #fff;
padding: 0;
}
}
}
/deep/.el-tabs__content {
width: 70%;
}
}
}
//
.tools-item {
display: flex;
position: relative;
width: 100%;
height: 48px;
align-items: center;
-webkit-box-align: center;
padding: 0 6px;
cursor: pointer;
font-size: 12px;
margin-bottom: 1px;
.tools-item-icon {
color: #409eff;
margin-right: 10px;
width: 53px;
height: 30px;
line-height: 30px;
text-align: center;
display: block;
border: 1px solid #3a4659;
background: #282a30;
}
.tools-item-text {
}
}
/deep/.el-tabs__content {
padding: 0;
}
}
.layout-middle {
// display: flex;
position: relative;
@ -107,50 +23,7 @@
align-items: center;
vertical-align: middle;
text-align: center;
.workbench-container {
position: relative;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
.vueRuler {
width: 100%;
padding: 18px 0px 0px 18px;
}
.workbench {
background-color: #1e1e1e;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
margin: 0;
padding: 0;
}
.bg-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: 30px 30px, 30px 30px;
background-image: linear-gradient(
hsla(0, 0%, 100%, 0.1) 1px,
transparent 0
),
linear-gradient(90deg, hsla(0, 0%, 100%, 0.1) 1px, transparent 0);
// z-index: 2;
}
}
}
.layout-right {
width: 300px;
}
@ -232,6 +105,4 @@
}
}
}
/deep/.el-dropdown-menu__item {
max-width: none;
}

@ -26,263 +26,280 @@ import Layout from '../views/layout/Layout'
* AuthKey: 'roleManage:find' 该页面进入的权限码
**/
export const constantRouterMap = [
{
path: '/login',
component: () => import('@/views/login'), hidden: true
{
path: '/login',
component: () => import('@/views/login'), hidden: true
},
{
{
path: '/aj/**',
component: () => import('@/views/bigScreenReport/aj'),
hidden: true
component: () => import('@/views/bigScreenReport/aj'),
hidden: true
},
{
path: '/el/**',
component: () => import('@/views/excelreport/el'),
hidden: true
},
{
path: '/index',
component: Layout,
name: 'index',
meta: {
title: '首页',
icon: 'iconhome2'
path: '/index',
component: Layout,
name: 'index',
meta: {
title: '首页',
icon: 'iconhome2'
},
children: [
{
path: '',
component: () => import('@/views/home/index'),
meta: {
title: '首页',
icon: 'iconhome2',
keepAlive: true,
requireAuth: true
}
{
path: '',
component: () => import('@/views/home/index'),
meta: {
title: '首页',
icon: 'iconhome2',
keepAlive: true,
requireAuth: true
}
},
]
},
{
path: '/access',
name: 'access',
component: Layout,
meta: {
title: '用户权限',
icon: 'icondfzq-',
path: '/access',
name: 'access',
component: Layout,
meta: {
title: '用户权限',
icon: 'icondfzq-',
requireAuth: true,
permission: 'authorityManage|roleManage|userManage'
permission: 'authorityManage|roleManage|userManage'
},
children: [
{
path: 'authority',
name: 'authority',
component: () => import('@/views/accessAuthority/index'),
meta: {
title: '权限管理',
icon: 'iconquanxian',
keepAlive: true,
requireAuth: true,
{
path: 'authority',
name: 'authority',
component: () => import('@/views/accessAuthority/index'),
meta: {
title: '权限管理',
icon: 'iconquanxian',
keepAlive: true,
requireAuth: true,
permission: 'authorityManage'
}
}
},
{
path: 'role',
name: 'role',
component: () => import('@/views/accessRole/index'),
meta: {
title: '角色管理',
icon: 'iconjiaose1',
keepAlive: true,
requireAuth: true,
{
path: 'role',
name: 'role',
component: () => import('@/views/accessRole/index'),
meta: {
title: '角色管理',
icon: 'iconjiaose1',
keepAlive: true,
requireAuth: true,
permission: 'roleManage'
}
}
},
{
path: 'user',
name: 'user',
component: () => import('@/views/accessUser/index'),
meta: {
title: '用户管理',
icon: 'iconyonghu',
keepAlive: true,
requireAuth: true,
{
path: 'user',
name: 'user',
component: () => import('@/views/accessUser/index'),
meta: {
title: '用户管理',
icon: 'iconyonghu',
keepAlive: true,
requireAuth: true,
permission: 'userManage'
}
}
},
]
},
{
path: '/report',
name: 'report',
component: Layout,
meta: {
title: '报表设计',
icon: 'iconnavicon-ywcs',
requireAuth: true,
permission: 'datasourceManage|resultsetManage|reportManage|bigScreenManage'
path: '/report',
name: 'report',
component: Layout,
meta: {
title: '报表设计',
icon: 'iconnavicon-ywcs',
requireAuth: true,
permission: 'datasourceManage|resultsetManage|reportManage|bigScreenManage'
},
children: [
{
path: 'datasource',
name: 'datasource',
component: () => import('@/views/datasource/index'),
meta: {
title: '数据源',
icon: 'icondatabase',
keepAlive: true,
requireAuth: true,
{
path: 'datasource',
name: 'datasource',
component: () => import('@/views/datasource/index'),
meta: {
title: '数据源',
icon: 'icondatabase',
keepAlive: true,
requireAuth: true,
permission: 'datasourceManage'
}
}
},
{
path: 'resultset',
name: 'resultset',
{
path: 'resultset',
name: 'resultset',
component: () => import('@/views/resultset/index'),
meta: {
title: '数据集',
icon: 'iconAPIwangguan',
keepAlive: true,
requireAuth: true,
meta: {
title: '数据集',
icon: 'iconAPIwangguan',
keepAlive: true,
requireAuth: true,
permission: 'resultsetManage'
}
}
},
{
path: 'report',
name: 'reportIndex',
component: () => import('@/views/reportManage/index'),
meta: {
title: '报表管理',
icon: 'iconnavicon-ywcs',
keepAlive: true,
requireAuth: true,
{
path: 'report',
name: 'reportIndex',
component: () => import('@/views/reportManage/index'),
meta: {
title: '报表管理',
icon: 'iconnavicon-ywcs',
keepAlive: true,
requireAuth: true,
permission: 'reportManage'
}
}
},
{
path: 'bigscreen',
name: 'bigscreen',
component: () => import('@/views/bigScreenReport/index'),
meta: {
title: '大屏报表',
icon: 'iconchufaqipeizhi-hui',
keepAlive: true,
requireAuth: true,
{
path: 'bigscreen',
name: 'bigscreen',
component: () => import('@/views/bigScreenReport/index'),
meta: {
title: '大屏报表',
icon: 'iconchufaqipeizhi-hui',
keepAlive: true,
requireAuth: true,
permission: 'bigScreenManage'
},
},
},
{
path: 'excelreport',
name: 'excelreport',
component: () => import('@/views/excelreport/index'),
meta: {
title: '表格报表',
icon: 'iconliebiao',
keepAlive: true,
requireAuth: true,
{
path: 'excelreport',
name: 'excelreport',
component: () => import('@/views/excelreport/index'),
meta: {
title: '表格报表',
icon: 'iconliebiao',
keepAlive: true,
requireAuth: true,
permission: 'excelManage'
}
}
},
{
path: 'reportshare',
name: 'reportshare',
component: () => import('@/views/reportShare/index'),
meta: {
title: '报表分享',
icon: 'iconfenxiang1',
keepAlive: true,
requireAuth: true,
permission: 'reportShareManage'
}
},
]
},
{
path: '/system',
name: 'system',
component: Layout,
meta: {
title: '系统设置',
icon: 'iconshezhi',
requireAuth: true,
permission: 'fileManage|dictManage|dictItemManage'
path: '/system',
name: 'system',
component: Layout,
meta: {
title: '系统设置',
icon: 'iconshezhi',
requireAuth: true,
permission: 'fileManage|dictManage|dictItemManage'
},
children: [
{
path: 'file',
name: 'file',
component: () => import('@/views/fileManagement/index'),
meta: {
title: '文件管理',
icon: 'iconfill_folder',
keepAlive: true,
requireAuth: true,
{
path: 'file',
name: 'file',
component: () => import('@/views/fileManagement/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,
{
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'),
hidden: true,
meta: {
title: '字典项',
icon: 'iconzidianxiang',
keepAlive: true,
requireAuth: true,
{
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/bigscreenDesigner/viewer'),
hidden: true,
meta: {
requireAuth: true
{
path: '/bigscreen/viewer',
component: () => import('@/views/bigscreenDesigner/viewer'),
hidden: true,
meta: {
requireAuth: true
}
},
{
path: '/bigscreen/designer',
component: () => import('@/views/bigscreenDesigner/designer'),
hidden: true,
meta: {
requireAuth: true
{
path: '/bigscreen/designer',
component: () => import('@/views/bigscreenDesigner/designer'),
hidden: true,
meta: {
requireAuth: true
}
},
{
path: '/excelreport/viewer',
component: () => import('@/views/excelreport/viewer'),
hidden: true,
meta: {
requireAuth: true
{
path: '/excelreport/viewer',
component: () => import('@/views/excelreport/viewer'),
hidden: true,
meta: {
requireAuth: true
}
},
{
path: '/excelreport/designer',
component: () => import('@/views/excelreport/designer'),
hidden: true,
meta: {
requireAuth: true
{
path: '/excelreport/designer',
component: () => import('@/views/excelreport/designer'),
hidden: true,
meta: {
requireAuth: true
}
},
// 重写大屏
{
path: '/screenDesigner',
path: '/screenDesigner',
component: () => import('@/views/screenDesigner/index'),
name: 'screenDesigner',
name: 'screenDesigner',
},
{
path: '/screen/preview',
component: () => import('@/views/screenDesigner/preview'),
hidden: true,
meta: {
requireAuth: true
path: '/screen/preview',
component: () => import('@/views/screenDesigner/preview'),
hidden: true,
meta: {
requireAuth: true
}
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '*',
redirect: '/login',
hidden: true
{
path: '*',
redirect: '/login',
hidden: true
},
]

@ -356,7 +356,7 @@ export const conversionCity = {
甘孜藏族自治州: [101.9623, 30.0495],
阿坝藏族羌族自治州: [102.2245, 31.8994],
雅安市: [103.0415, 30.0099],
天津城区: [117.1901, 39.1255],
天津: [117.2015, 39.0853],
赣州市: [114.9334, 25.8311],
景德镇市: [117.1848, 29.2744],
萍乡市: [113.8871, 27.6587],

@ -114,6 +114,13 @@ export default {
default: () => {
return "";
}
},
reportType: {
required : true,
type: String,
default: () =>{
return "";
}
}
},
data() {
@ -124,6 +131,7 @@ export default {
dialogForm: {
shareValidType: 0,
reportCode: "",
reportType: "",
shareUrl: "",
shareCode: "",
sharePassword: "",
@ -164,12 +172,11 @@ export default {
this.dialogForm.sharePassword = "";
},
async createShare() {
this.dialogForm.reportType = this.reportType;
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: "创建链接成功!",

@ -101,6 +101,7 @@
:visib="visibleForShareDialog"
:reportCode="reportCodeForShareDialog"
:reportName="reportNameForShareDialog"
:reportType="reportTypeForShareDialog"
@handleClose="visibleForShareDialog = false"
/>
</div>
@ -130,7 +131,8 @@ export default {
//
visibleForShareDialog: false,
reportCodeForShareDialog: "",
reportNameForShareDialog: ""
reportNameForShareDialog: "",
reportTypeForShareDialog: "",
};
},
mounted() {},
@ -176,10 +178,12 @@ export default {
share(val) {
this.reportCodeForShareDialog = val.reportCode;
this.reportNameForShareDialog = val.reportName;
this.reportTypeForShareDialog = val.reportType;
this.visibleForShareDialog = true;
},
openDesign(val) {
let routeUrl = this.$router.resolve({
//path: "/screenDesigner",
path: "/bigscreen/designer",
query: {
reportCode: val.reportCode

@ -420,7 +420,7 @@ export const widgetMoreBarLine = {
{
type: 'el-input-number',
label: '左坐标字号',
name: 'namefontSizeYLeft',
name: 'nameFontSizeYLeft',
required: false,
placeholder: '',
value: 14,
@ -452,7 +452,7 @@ export const widgetMoreBarLine = {
{
type: 'el-input-number',
label: '右坐标字号',
name: 'namefontSizeYRight',
name: 'nameFontSizeYRight',
required: false,
placeholder: '',
value: 14,
@ -650,7 +650,6 @@ export const widgetMoreBarLine = {
required: false,
value: [
{color: '#4bdfff'},
{color: '#5dc1fd'},
{color: '#55f49c'},
{color: '#ffa43a'},
],
@ -697,14 +696,14 @@ export const widgetMoreBarLine = {
relactiveDom: 'dataType',
relactiveDomValue: 'staticData',
value: [
{"date": "2014", "unsales": 400, "manus": 300, "rework": 400, "sales": 4.2,},
{"date": "2015", "unsales": 400, "manus": 500, "rework": 300, "sales": 3.6,},
{"date": "2016", "unsales": 300, "manus": 500, "rework": 500, "sales": 5.8,},
{"date": "2017", "unsales": 300, "manus": 500, "rework": 700, "sales": 3.4,},
{"date": "2018", "unsales": 400, "manus": 400, "rework": 1000, "sales": 2.5,},
{"date": "2019", "unsales": 400, "manus": 500, "rework": 500, "sales": 5.8,},
{"date": "2020", "unsales": 300, "manus": 400, "rework": 600, "sales": 7.6,},
{"date": "2021", "unsales": 300, "manus": 600, "rework": 400, "sales": 3.4,},
{"date": "2014", "unsales": 400, "manus": 300, "sales": 4.2,},
{"date": "2015", "unsales": 400, "manus": 500, "sales": 3.6,},
{"date": "2016", "unsales": 300, "manus": 500, "sales": 5.8,},
{"date": "2017", "unsales": 300, "manus": 500, "sales": 3.4,},
{"date": "2018", "unsales": 400, "manus": 400, "sales": 2.5,},
{"date": "2019", "unsales": 400, "manus": 500, "sales": 5.8,},
{"date": "2020", "unsales": 300, "manus": 400, "sales": 7.6,},
{"date": "2021", "unsales": 300, "manus": 600, "sales": 3.4,},
]
},
{

@ -80,7 +80,6 @@ export default {
nameTextStyle: {
color: '#666666'
},
position: 'right',
axisLine: {
lineStyle: {
color: '#cdd5e2'
@ -283,9 +282,9 @@ export default {
type: 'value',
show: optionsSetup.isShowYLeft, //
name: optionsSetup.textNameYLeft, //
nameTextStyle: { //
nameTextStyle: { //
color: optionsSetup.nameColorYLeft,
fontSize: optionsSetup.namefontSizeYLeft
fontSize: optionsSetup.nameFontSizeYLeft
},
axisLabel: {
show: true,
@ -295,7 +294,7 @@ export default {
}
},
axisLine: {
show: optionsSetup.isShowYLeft,
show: true,
lineStyle: {
color: optionsSetup.lineColorY
}
@ -306,12 +305,11 @@ export default {
},
{
type: 'value',
position: 'right',
show: optionsSetup.isShowYRight, //
name: optionsSetup.textNameYRight, //
nameTextStyle: { //
nameTextStyle: { //
color: optionsSetup.nameColorYRight,
fontSize: optionsSetup.namefontSizeYRight
fontSize: optionsSetup.nameFontSizeYRight
},
axisLabel: {
show: true,
@ -321,7 +319,7 @@ export default {
}
},
axisLine: {
show: optionsSetup.isShowYRight,
show: true,
lineStyle: {
color: optionsSetup.lineColorY
}
@ -423,13 +421,11 @@ export default {
let axis = [];
let bar1 = [];
let bar2 = [];
let bar3 = [];
let line = [];
for (const i in val) {
axis[i] = val[i].date;
bar1[i] = val[i].unsales;
bar2[i] = val[i].manus;
bar3[i] = val[i].rework;
line[i] = val[i].sales;
}
// x
@ -476,12 +472,10 @@ export default {
}
series[0].data = bar1;
series[1].data = bar2;
series[2].data = bar3;
series[3].data = line;
const legendName = [];
legendName.push('调解成功');
legendName.push('调解失败');
legendName.push('调解终止');
legendName.push('调解成功率');
this.options.legend['data'] = legendName;
this.setOptionsLegendName(legendName);

@ -54,7 +54,11 @@
<el-form-item label="链接" prop="reportShareUrl">
<el-input v-model="reportShareUrl" :disabled="true" />
</el-form-item>
<el-form-item label="分享码" prop="sharePassword">
<el-form-item
v-if="dialogForm.sharePasswordFlag"
label="分享码"
prop="sharePassword"
>
<el-input v-model="dialogForm.sharePassword" :disabled="true" />
</el-form-item>
@ -110,6 +114,13 @@ export default {
default: () => {
return "";
}
},
reportType: {
required : true,
type: String,
default: () =>{
return "";
}
}
},
data() {
@ -120,6 +131,7 @@ export default {
dialogForm: {
shareValidType: 0,
reportCode: "",
reportType: "",
shareUrl: "",
shareCode: "",
sharePassword: "",
@ -160,12 +172,12 @@ export default {
this.dialogForm.sharePassword = "";
},
async createShare() {
this.dialogForm.reportType = this.reportType;
this.dialogForm.reportCode = this.reportCode;
this.dialogForm.shareUrl = window.location.href;
// console.log(this.dialogForm)
console.log(this.dialogForm)
const { code, data } = await reportShareAdd(this.dialogForm);
if (code != "200") return;
// console.log(data)
this.shareLinkFlag1 = false;
this.$message({
message: "创建链接成功!",

@ -0,0 +1,85 @@
<!--
* @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>
<el-dialog
title="请输入分享码"
:visible.sync="dialogVisible"
width="30%"
:close-on-click-modal="false"
:before-close="handleClose"
>
<el-input v-model="password" placeholder="请输入分享码"></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="checkPassword()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { reportShareDetailByCode } from "@/api/reportShare";
import { setShareToken } from "@/utils/auth";
export default {
name: "Excel",
components: {},
data() {
return {
password: "",
sharePassword: "",
dialogVisible: false,
reportCode: "",
shareToken: ""
};
},
created() {
this.handleOpen();
},
methods: {
async handleOpen() {
const url = window.location.href;
const shareCode = url.substring(url.lastIndexOf("/") + 1);
const { code, data } = await reportShareDetailByCode(shareCode);
if (code != "200") return;
this.reportCode = data.reportCode;
this.sharePassword = data.sharePassword;
this.shareToken = data.shareToken;
if (this.sharePassword) {
this.dialogVisible = true;
} else {
this.pushEl();
}
},
checkPassword() {
const md5 = require("js-md5");
const inputPassword = md5(this.password);
if (inputPassword == this.sharePassword) {
this.pushEl();
} else {
this.$message.error("分享码输入不正确");
}
},
pushEl() {
setShareToken(this.shareToken);
this.$router.push({
path: "/excelreport/viewer",
query: {
reportCode: this.reportCode
}
});
},
handleClose(done) {
this.$confirm("确认关闭?")
.then(_ => {
done();
})
.catch(_ => {});
}
}
};
</script>

@ -101,6 +101,7 @@
:visib="visibleForShareDialog"
:reportCode="reportCodeForShareDialog"
:reportName="reportNameForShareDialog"
:reportType="reportTypeForShareDialog"
@handleClose="visibleForShareDialog = false"
/>
</div>
@ -130,7 +131,8 @@ export default {
//
visibleForShareDialog: false,
reportCodeForShareDialog: "",
reportNameForShareDialog: ""
reportNameForShareDialog: "",
reportTypeForShareDialog: ""
};
},
mounted() {},
@ -174,12 +176,9 @@ export default {
},
//
share(val) {
//excel
this.$message.warning("暂不支持excel报表分享");
return;
this.reportCodeForShareDialog = val.reportCode;
this.reportNameForShareDialog = val.reportName;
this.reportTypeForShareDialog = val.reportType;
this.visibleForShareDialog = true;
},
openDesign(val) {

@ -7,21 +7,36 @@
class="hamburger-container"
/>
<breadcrumb />
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<i class="icon iconfont iconyonghu user" />
<span class="user-name">{{ operatorText }}</span>
<i class="el-icon-caret-bottom" />
<div class="right-menu">
<div class="item-men">
<div class="item" @click="centerDialogVisible = true">说明</div>
<div class="item">
<a href="https://ajreport.beliefteam.cn/report-doc/" target="blank"
>文档</a
>
</div>
<div class="item">
<a href="https://gitee.com/anji-plus/report" target="blank">社区</a>
</div>
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<el-dropdown-item divided>
<span style="display:block;" @click="updatePassword"></span>
</el-dropdown-item>
<el-dropdown-item divided>
<span style="display:block;" @click="logout"></span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<i class="icon iconfont iconyonghu user" />
<span class="user-name">{{ operatorText }}</span>
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<el-dropdown-item divided>
<span style="display:block;" @click="updatePassword"></span>
</el-dropdown-item>
<el-dropdown-item divided>
<span style="display:block;" @click="logout"></span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-menu>
<!-- 修改密码弹框 -->
<el-dialog
@ -67,6 +82,30 @@
<el-button type="primary" @click="confrimUpdate"> </el-button>
</span>
</el-dialog>
<!--说明弹出框-->
<el-dialog
title="说明"
:visible.sync="centerDialogVisible"
width="34%"
center
>
<div style="font-size: 30px; line-height: 50px; margin-bottom: 50px">
AJ-Report由<a href="http://www.anji-plus.com/" target="_blank" style="text-decoration: underline"><b>安吉加加信息技术有限公司</b></a
>遵循 <a href="http://www.apache.org/licenses/LICENSE-2.0.html" target="_blank" style="word-wrap: break-word"><strong style="color: orangered">Apache2.0开源协议</strong></a
><a href="https://gitee.com/explore" target="_blank" style="text-decoration: underline; word-wrap: break-word"><b>Gitee平台</b></a
>进行开源
</div>
<div style="font-size: 30px; line-height: 50px">
<strong style="color: orangered">个人/商业使用须遵循Apache2.0开源协议</strong>
<strong style="color: orangered">禁止将AJ-Report产品用于违法违规业务</strong>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="centerDialogVisible = false"
> </el-button
>
</span>
</el-dialog>
</div>
</template>
@ -127,7 +166,10 @@ export default {
confirmPassword: [
{ required: true, validator: validatePass3, trigger: "blur" }
]
}
},
//
centerDialogVisible: false
};
},
components: {
@ -204,6 +246,8 @@ export default {
line-height: 50px;
border-radius: 0px !important;
background: #fff !important;
display: flex;
flex-direction: row;
.hamburger-container {
line-height: 57px;
height: 49px;
@ -217,15 +261,25 @@ export default {
top: 16px;
color: red;
}
.right-menu {
position: absolute;
right: 35px;
display: flex;
.item-men {
display: flex;
flex-direction: row;
.item {
margin-right: 60px;
cursor: pointer;
}
}
}
.avatar-container {
height: 50px;
display: inline-block;
position: absolute;
right: 35px;
.avatar-wrapper {
line-height: 50px;
cursor: pointer;
margin-top: 5px;
position: relative;
.user-avatar {
width: 40px;

@ -3,7 +3,7 @@
<div class="admin-title" @click="goBigScreen">
<div class="con">
<img src="../../../../../static/logo-dp.png" width="50" />
<span class="version">V0.9.7.3</span>
<span class="version">V0.9.8</span>
</div>
</div>
<el-menu

@ -1,16 +1,33 @@
<!--
* @Descripttion: 登录
* @version:
* @version:
* @Author: qianlishi
* @Date: 2021-12-11 14:48:27
* @LastEditors: qianlishi
* @LastEditTime: 2021-12-13 09:45:44
* @LastEditTime: 2022-06-23 17:23:23
-->
<template>
<div class="login_container">
<!-- 顶部logo -->
<div class="login_title">
<img src="@/assets/images/home-logo.png" alt="logo" />
<div class="left">
<div class="box">
<img src="../../static/logo-dp.png" alt="" />
</div>
<div class="name">AJ-Report</div>
</div>
<div class="right">
<div class="item" @click="centerDialogVisible = true">说明</div>
<div class="item">
<a href="https://ajreport.beliefteam.cn/report-doc/" target="blank"
>文档</a
>
</div>
<div class="item">
<a href="https://gitee.com/anji-plus/report" target="blank">社区</a>
</div>
</div>
<!-- <img src="@/assets/images/home-logo.png" alt="logo" /> -->
</div>
<div class="login_contant">
<img src="@/assets/images/login.jpg" alt="image" class="login_img" />
@ -109,6 +126,29 @@
:img-size="{ width: '400px', height: '200px' }"
@success="verifylogin"
/>
<el-dialog
title="说明"
:visible.sync="centerDialogVisible"
width="34%"
center
>
<div style="font-size: 30px; line-height: 50px; margin-bottom: 50px">
AJ-Report由<a href="http://www.anji-plus.com/" target="_blank" style="text-decoration: underline"><b>安吉加加信息技术有限公司</b></a
>遵循 <a href="http://www.apache.org/licenses/LICENSE-2.0.html" target="_blank" style="word-wrap: break-word"><strong style="color: orangered">Apache2.0开源协议</strong></a
><a href="https://gitee.com/explore" target="_blank" style="text-decoration: underline; word-wrap: break-word"><b>Gitee平台</b></a
>进行开源
</div>
<div style="font-size: 30px; line-height: 50px">
<strong style="color: orangered">个人/商业使用须遵循Apache2.0开源协议</strong>
<strong style="color: orangered">禁止将AJ-Report产品用于违法违规业务</strong>
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="centerDialogVisible = false"
> </el-button
>
</span>
</el-dialog>
</div>
</template>
@ -142,7 +182,8 @@ export default {
loading: false,
redirect: undefined,
otherQuery: {},
needCaptcha: false
needCaptcha: false,
centerDialogVisible: false
};
},
watch: {
@ -314,10 +355,47 @@ export default {
height: 60px;
padding: 10px 60px;
display: flex;
justify-content: space-between;
align-items: center;
img {
width: 10%;
display: block;
.left {
display: flex;
flex-direction: row;
.box {
width: 40px;
height: 40px;
margin-top: 6px;
img {
width: 100%;
height: 100%;
}
}
.name {
font-size: 20px;
font-weight: bold;
padding-bottom: 5px;
margin-left: 10px;
border-left: 1px solid #ccc;
padding-top: 14px;
padding-left: 10px;
}
.box1 {
width: 100px;
margin-left: 40px;
cursor: pointer;
img {
width: 100%;
margin-top: 10px;
margin-left: 10px;
}
}
}
.right {
display: flex;
flex-direction: row;
.item {
margin-right: 60px;
cursor: pointer;
}
}
}
.login_contant {

@ -114,6 +114,13 @@ export default {
default: () => {
return "";
}
},
reportType: {
required: true,
type: String,
default:() =>{
return "";
}
}
},
data() {
@ -124,6 +131,7 @@ export default {
dialogForm: {
shareValidType: 0,
reportCode: "",
reportType: "",
shareUrl: "",
shareCode: "",
sharePassword: "",
@ -164,12 +172,11 @@ export default {
this.dialogForm.sharePassword = "";
},
async createShare() {
this.dialogForm.reportType = this.reportType;
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: "创建链接成功!",

@ -13,6 +13,7 @@
:visib="visibleForShareDialog"
:reportCode="reportCodeForShareDialog"
:reportName="reportNameForShareDialog"
:reportType="reportTypeForShareDialog"
@handleClose="visibleForShareDialog = false"
/>
<copyDialog :visib.sync="copyVisible" :rowData="rowData" @close="close" />
@ -45,6 +46,7 @@ export default {
visibleForShareDialog: false,
reportCodeForShareDialog: "",
reportNameForShareDialog: "",
reportTypeForShareDialog: "",
crudOption: {
// 使
title: "报表管理",
@ -354,13 +356,9 @@ export default {
},
//
shareReport(val) {
if (val.reportType == "report_excel") {
//excel
this.$message.warning("暂不支持excel报表分享");
return;
}
this.reportCodeForShareDialog = val.reportCode;
this.reportNameForShareDialog = val.reportName;
this.reportTypeForShareDialog = val.reportType;
this.visibleForShareDialog = true;
},
//

@ -0,0 +1,259 @@
<template>
<anji-crud ref="listPage" :option="crudOption">
<template v-slot:pageSection>
</template>
</anji-crud>
</template>
<script>
import {
reportShareDeleteBatch,
reportShareDetail,
reportShareList,
reportShareDelay, reportShareAdd
} from "@/api/reportShare";
export default {
name: "Report",
components: {
anjiCrud: require("@/components/AnjiPlus/anji-crud/anji-crud").default,
},
data() {
return {
crudOption: {
// 使
title: '报表分享',
//
labelWidth: '120px',
//
queryFormFields: [
{
inputType: "input",
label: "分享编码",
field: "shareCode"
},
{
inputType: "input",
label: "报表编码",
field: "reportCode"
},
{
inputType: "anji-select",
anjiSelectOption: {
dictCode: "SHARE_VAILD"
},
label: "分享类型",
field: "shareValidType"
},
],
//
tableButtons: [
{
label: "删除",
type: "danger",
permission: "reportShareManage:delete",
icon: "el-icon-delete",
plain: false,
click: () => {
return this.$refs.listPage.handleDeleteBatch();
}
}
],
//
rowButtons: [
{
label: "复制url",
click: this.copyUrlPath
},
{
label: "删除",
permission: "reportShareManage:delete",
click: row => {
return this.$refs.listPage.handleDeleteBatch(row);
}
},
{
label: "延期1天",
permission: "reportShareManage:shareDelay",
click: this.shareDelay_1
},
{
label: "延期1周",
permission: "reportShareManage:shareDelay",
click: this.shareDelay_7
},
{
label: "延期1月",
permission: "reportShareManage:shareDelay",
click: this.shareDelay_30
},
],
//
buttons: {
query: {
api: reportShareList,
permission: 'ReportShare:query',
sort: "create_time",
order: "DESC"
},
queryByPrimarykey: {
api: reportShareDetail,
permission: 'ReportShare:detail'
},
delete: {
api: reportShareDeleteBatch,
permission: 'ReportShare:delete'
},
rowButtonsWidth: 150 // row
},
//
columns: [
{
label: '',
field: 'id',
primaryKey: true, // ,
tableHide: true, //
editHide: true, //
},
{
label: '报表编码',//
placeholder: '',
field: 'reportCode',
editField: 'reportCode',
inputType: 'input',
rules: [
{ min: 1, max: 50, message: '不超过50个字符', trigger: 'blur' }
],
disabled: false,
},
{
label: '分享编码',//UUID
placeholder: '',
field: 'shareCode',
editField: 'shareCode',
inputType: 'input',
rules: [
{ min: 1, max: 50, message: '不超过50个字符', trigger: 'blur' }
],
disabled: false,
},
{
label: '分享类型',//DIC_NAME=SHARE_VAILD
placeholder: '',
field: 'shareValidType',
fieldTableRowRenderer: row => {
return this.getDictLabelByCode("SHARE_VAILD", row["shareValidType"]);
},
editField: 'shareValidType',
inputType: 'input',
rules: [
],
disabled: false,
},
{
label: '分享过期时间',//
placeholder: '',
field: 'shareValidTime',
editField: 'shareValidTime',
inputType: 'input',
rules: [
],
disabled: false,
},
{
label: '分享token',//token
placeholder: '',
field: 'shareToken',
editField: 'shareToken',
tableHide: true,
inputType: 'input',
rules: [
{ min: 1, max: 255, message: '不超过255个字符', trigger: 'blur' }
],
disabled: false,
},
{
label: '分享url',//url
placeholder: '',
field: 'shareUrl',
editField: 'shareUrl',
inputType: 'input',
rules: [
{ min: 1, max: 100, message: '不超过100个字符', trigger: 'blur' }
],
disabled: false,
},
{
label: '分享码',
placeholder: '',
field: 'sharePassword',
editField: 'sharePassword',
inputType: 'input',
rules: [
],
disabled: false,
},
],
//
// formData fieldName, fieldVal, fieldExtend
// fieldName input name
// fieldVal input
// fieldExtend select
formChange: (formData, fieldName, fieldVal, fieldExtend) => {
}
},
//
copyVisible: false,
rowData: {}
};
},
created() {},
methods: {
handleOpenDialog1() {
alert("自定义按钮1点击事件");
},
shareDelay_1(val) {
this.shareDelay(val.id, 1)
},
shareDelay_7(val) {
this.shareDelay(val.id, 7)
},
shareDelay_30(val) {
this.shareDelay(val.id, 30)
},
async shareDelay(shareId, shareValidType) {
const param = {}
param['id'] = shareId
param['shareValidType'] = shareValidType
const {code} = await reportShareDelay(param);
if (code != "200") return;
this.$message({
message: "延期成功!",
type: "success"
});
this.$refs.listPage.handleQueryForm("query");
},
copyUrlPath(val) {
this.copyToClip(val.shareUrl);
this.$message({
message: "已将url路径复制至剪切板",
type: "success"
});
},
copyToClip(content, message) {
let aux = document.createElement("input");
aux.setAttribute("value", content);
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
}
}
};
</script>

@ -24,7 +24,9 @@
export default {
props: {
styleObj: Object,
visible: Boolean
visible: Boolean,
widgets: Array,
rightClickIndex: Number
},
data() {
return {};
@ -49,7 +51,8 @@ export default {
type: "warning"
})
.then(() => {
this.$emit("deletelayer");
console.log(this.rightClickIndex);
this.widgets.splice(this.rightClickIndex, 1);
this.$message({
type: "success",
message: "删除成功!"
@ -63,19 +66,41 @@ export default {
});
},
copyLayer() {
this.$emit("copylayer");
const obj = this.deepClone(this.widgets[this.rightClickIndex]);
this.widgets.splice(this.widgets.length, 0, obj);
},
istopLayer() {
this.$emit("istopLayer");
if (this.rightClickIndex + 1 < this.widgets.length) {
const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
this.widgets.push(temp);
}
},
setlowLayer() {
this.$emit("setlowLayer");
if (this.rightClickIndex != 0) {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
moveupLayer() {
this.$emit("moveupLayer");
if (this.rightClickIndex != 0) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex - 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.push(this.widgets.shift());
}
},
movedownLayer() {
this.$emit("movedownLayer");
if (this.rightClickIndex != this.widgets.length - 1) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex + 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
}
}
};

@ -12,6 +12,7 @@
* 词云图{type: 'wordCloud',tabName: '词云图'}
* **/
import { screenConfig } from './texts/screenConfig'
import {widgetHref} from "./texts/widget-href"
import {widgetIframe} from "./texts/widget-iframe"
import {widgetImage} from "./texts/widget-image"
@ -43,6 +44,7 @@ import {widgetHeatmap} from "./heatmap/widget-heatmap";
export const widgetTool = [
screenConfig,
widgetHref,
widgetIframe,
widgetImage,

@ -1,3 +1,11 @@
/*
* @Descripttion:
* @version:
* @Author: qianlishi
* @Date: 2022-03-14 14:05:15
* @LastEditors: qianlishi
* @LastEditTime: 2022-06-17 17:24:33
*/
import {converArr} from '../util/common'
import { widgetTool } from "./configs"

@ -1,218 +1,48 @@
<template>
<div class="layout">
<!-- 操作栏 -->
<div class="layout-bar">
<div class="bar-item" @click="saveData">
<i class="iconfont iconsave"></i>保存
</div>
<div class="bar-item" @click="viewScreen">
<i class="iconfont iconyulan"></i>预览
</div>
<div class="bar-item" @click="handleUndo">
<i class="iconfont iconundo"></i>撤销
</div>
<div class="bar-item" @click="handleRedo">
<i class="iconfont iconhuifubeifen"></i>恢复
</div>
<div class="bar-item">
<el-upload
class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1"
>
<i class="iconfont icondaoru"></i>
</el-upload>
导入
</div>
<div class="bar-item">
<i class="iconfont icondaochu"></i>
<el-dropdown @command="exportDashboard">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1">导出(包含数据集)</el-dropdown-item>
<el-dropdown-item command="0">导出(不包含数据集)</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<div class="layout-container">
<!-- 图表 -->
<el-tabs class="layout-left" type="border-card">
<el-tab-pane>
<span slot="label"><i class="el-icon-date icon"></i>工具栏</span>
<div class="chart-type">
<el-tabs class="type-left" tab-position="left">
<el-tab-pane
v-for="(item, index) in widgetTools"
:key="index"
:label="item.name"
>
<draggable
v-for="(it, idx) in item.list"
:key="idx"
@end="evt => widgetOnDragged(evt, it.code)"
>
<div class="tools-item">
<span class="tools-item-icon">
<i class="iconfont" :class="it.icon"></i>
</span>
<span class="tools-item-text">{{ it.label }}</span>
</div>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label" class="icon"
><i class="el-icon-date icon"></i>图层</span
>
<div
v-for="(item, index) in layerWidget"
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
</span>
<span class="tools-item-text">{{ item.label }}</span>
</div>
</el-tab-pane>
</el-tabs>
<!-- 设计器 -->
<!-- 左侧菜单 -->
<left-menu
:layerWidget="layerWidget"
:widgetTools="widgetTools"
:widgetIndex="widgetIndex"
@widgetOnDragged="widgetOnDragged"
@layerClick="layerClick"
/>
<div
class="layout-middle"
:style="{ width: middleWidth + 'px', height: middleHeight + 'px' }"
:style="{
width: middleWidth + 'px',
height: middleHeight + 'px'
}"
>
<div
class="workbench-container"
:style="{
width: bigscreenWidthInWorkbench + 'px',
height: bigscreenHeightInWorkbench + 'px'
}"
@mousedown="handleMouseDown"
>
<vue-ruler-tool
v-model="dashboard.presetLine"
class="vueRuler"
:step-length="50"
:parent="true"
:position="'relative'"
:is-scale-revise="true"
:visible.sync="dashboard.presetLineVisible"
>
<div
id="workbench"
class="workbench"
:style="{
transform: workbenchTransform,
width: bigscreenWidth + 'px',
height: bigscreenHeight + 'px',
'background-color': dashboard.backgroundColor,
'background-image': 'url(' + dashboard.backgroundImage + ')',
'background-position': '0% 0%',
'background-size': '100% 100%',
'background-repeat': 'initial',
'background-attachment': 'initial',
'background-origin': 'initial',
'background-clip': 'initial'
}"
@click.self="setOptionsOnClickScreen"
>
<div v-if="grade" class="bg-grid"></div>
<widget
ref="widgets"
v-for="(widget, index) in widgets"
:key="index"
v-model="widget.value"
:index="index"
:step="step"
:type="widget.type"
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mouseup.prevent.native="widgetsMouseup"
/>
</div>
</vue-ruler-tool>
</div>
</div>
<!-- 顶部按钮 -->
<top-bar
:dashboard="dashboard"
:widgets="widgets"
@refresh="initEchartData"
/>
<!-- 设置 -->
<div class="layout-right">
<el-tabs v-model="activeName" type="border-card" :stretch="true">
<el-tab-pane
v-if="
isNotNull(widgetOptions.setup) ||
isNotNull(widgetOptions.collapse)
"
name="first"
label="配置"
>
<dynamicForm
ref="formData"
:options="widgetOptions.setup"
@onChanged="val => widgetValueChanged('setup', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.data)"
name="second"
label="数据"
>
<dynamicForm
ref="formData"
:options="widgetOptions.data"
@onChanged="val => widgetValueChanged('data', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.position)"
name="third"
label="坐标"
>
<dynamicForm
ref="formData"
:options="widgetOptions.position"
@onChanged="val => widgetValueChanged('position', val)"
/>
</el-tab-pane>
</el-tabs>
<!-- 设计器 -->
<middleScreen
ref="middleScreen"
:middleWidth="middleWidth"
:middleHeight="middleHeight"
:dashboard="dashboard"
:widgets="widgets"
@change="changeOptions"
/>
</div>
<!-- 右侧配置 -->
<right-config
:widgetOptions="widgetOptions"
@widgetValueChanged="widgetValueChanged"
/>
</div>
<content-menu
:visible.sync="visibleContentMenu"
:style-obj="styleObj"
@deletelayer="deletelayer"
@copylayer="copylayer"
@istopLayer="istopLayer"
@setlowLayer="setlowLayer"
@moveupLayer="moveupLayer"
@movedownLayer="movedownLayer"
/>
</div>
</template>
<script>
import {
insertDashboard,
detailDashboard,
importDashboard,
exportDashboard
} from "@/api/bigscreen";
import { detailDashboard, importDashboard } from "@/api/bigscreen";
import {
swapArr,
setDefaultValue,
@ -222,42 +52,31 @@ import {
handleBigScreen,
handlerLayerWidget
} from "./util/screen";
import { screenConfig } from "./config/texts/screenConfig.js";
import { widgetTools, getToolByCode } from "./config/index.js";
import VueRulerTool from "vue-ruler-tool"; //
import widget from "./widget/index.vue";
import dynamicForm from "./components/dynamicForm.vue";
import draggable from "vuedraggable";
import contentMenu from "./components/contentMenu";
import { getToken } from "@/utils/auth";
import { Revoke } from "@/utils/revoke"; // 2022-02-22
import leftMenu from "./layout/leftMenu.vue";
import topBar from "./layout/topBar.vue";
import middleScreen from "./layout/middleScreen.vue";
import rightConfig from "./layout/rightConfig.vue";
export default {
components: {
VueRulerTool,
widget,
dynamicForm,
draggable,
contentMenu
leftMenu,
topBar,
middleScreen,
rightConfig
},
data() {
return {
uploadUrl:
process.env.BASE_API +
"/reportDashboard/import/" +
this.$route.query.reportCode,
grade: false,
//
layerWidget: [],
widgetTools: widgetTools, // js
widthLeftForTools: 200, //
widthLeftForToolsHideButton: 15, //
widthLeftForOptions: 300, //
widthPaddingTools: 18,
toolIsShow: true, //
widgetTools: widgetTools,
widgetIndex: 0,
bigscreenWidth: 1920, //
bigscreenHeight: 1080,
revoke: null, // 2022-02-22
//
//
grade: false,
// gaea_report_dashboard
dashboard: {
id: null,
@ -266,10 +85,13 @@ export default {
height: 1080, //
backgroundColor: "", //
backgroundImage: "", //
refreshSeconds: null, //
presetLine: [], // 线
presetLineVisible: true // 线
refreshSeconds: null //
},
//
widthLeftForTools: 200, //
widthRightForOptions: 300, //
//
screenCode: "",
//
@ -293,105 +115,42 @@ export default {
}
], //
//
widgetIndex: 0,
//
widgetOptions: {
setup: [], //
data: [], //
position: [] //
},
flagWidgetClickStopPropagation: false, // click
styleObj: {
left: 0,
top: 0
},
visibleContentMenu: false,
rightClickIndex: -1,
activeName: "first"
};
},
computed: {
step() {
return Number(100 / (this.bigscreenScaleInWorkbench * 100));
},
headers() {
return {
Authorization: getToken() // token
};
},
//
middleWidth() {
let widthLeftAndRight = 0;
if (this.toolIsShow) {
widthLeftAndRight += this.widthLeftForTools; //
setup: [],
data: [],
position: []
}
widthLeftAndRight += this.widthLeftForToolsHideButton; //
widthLeftAndRight += this.widthLeftForOptions; //
let middleWidth = this.bodyWidth - widthLeftAndRight;
return middleWidth;
},
middleHeight() {
return this.bodyHeight;
},
//
bigscreenScaleInWorkbench() {
let widthScale =
(this.middleWidth - this.widthPaddingTools) / this.bigscreenWidth;
let heightScale =
(this.middleHeight - this.widthPaddingTools) / this.bigscreenHeight;
return Math.min(widthScale, heightScale);
},
workbenchTransform() {
return `scale(${this.bigscreenScaleInWorkbench}, ${
this.bigscreenScaleInWorkbench
})`;
},
//
bigscreenWidthInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenWidth) +
this.widthPaddingTools
);
},
bigscreenHeightInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenHeight) +
this.widthPaddingTools
);
}
};
},
watch: {
widgets: {
handler(val) {
this.layerWidget = handlerLayerWidget(val, getToolByCode);
//
this.$nextTick(() => {
this.revoke.push(this.widgets);
});
},
deep: true
}
},
created() {
/* 以下是记录历史的 */
this.revoke = new Revoke();
computed: {
//
middleWidth() {
return (
this.bodyWidth - this.widthLeftForTools - this.widthRightForOptions
);
},
//
middleHeight() {
return this.bodyHeight;
}
},
mounted() {
this.initScreen();
//
// this.initEchartData();
this.widgets = [];
window.addEventListener("mouseup", () => {
this.grade = false;
});
this.initEchartData();
},
methods: {
//
initScreen() {
this.widgetOptions = screenConfig["options"];
},
// echrats
async initEchartData() {
const reportCode = this.$route.query.reportCode;
@ -445,71 +204,17 @@ export default {
//
const widgetJsonValue = handleDefaultValue(widgetJson);
//20220222
widgetJsonValue.value.position.left =
x - widgetJsonValue.value.position.width / 2;
widgetJsonValue.value.position.top =
y - widgetJsonValue.value.position.height / 2;
//
this.widgets.push(this.deepClone(widgetJsonValue));
//
this.setOptionsOnClickWidget(this.widgets.length - 1);
},
//
setOptionsOnClickScreen() {
this.screenCode = "screen";
//
this.activeName = "first";
this.widgetOptions = screenConfig["options"];
this.$refs.middleScreen.setOptionsOnClickWidget(this.widgets.length - 1);
},
layerClick(index) {
this.widgetIndex = index;
this.widgetsClick(index);
},
//
setOptionsOnClickWidget(obj) {
console.log(obj);
this.screenCode = "";
if (typeof obj == "number") {
this.widgetOptions = this.deepClone(this.widgets[obj]["options"]);
return;
}
if (obj.index < 0 || obj.index >= this.widgets.length) {
return;
}
this.widgetIndex = obj.index;
this.widgets[obj.index].value.position = obj;
this.widgets[obj.index].options.position.forEach(el => {
for (const key in obj) {
if (el.name == key) {
el.value = obj[key];
}
}
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
},
widgetsClick(index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
this.$refs.widgets[i].$refs.draggable.setActive(true);
} else {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
}
this.setOptionsOnClickWidget(index);
this.grade = true;
},
widgetsMouseup(e) {
this.grade = false;
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
this.$refs.middleScreen.widgetsClick(index);
},
//
widgetValueChanged(key, val) {
if (this.screenCode == "screen") {
@ -539,176 +244,12 @@ export default {
}
}
},
rightClick(event, index) {
this.rightClickIndex = index;
const left = event.clientX;
const top = event.clientY;
if (left || top) {
this.styleObj = {
left: left + "px",
top: top + "px",
display: "block"
};
}
this.visibleContentMenu = true;
return false;
changeOptions(widgetOptions) {
this.widgetOptions = widgetOptions;
},
datadragEnd(evt) {
evt.preventDefault();
this.widgets = swapArr(this.widgets, evt.oldIndex, evt.newIndex);
},
//
async saveData() {
if (!this.widgets || this.widgets.length == 0) {
this.$message.error("请添加组件");
return;
}
const screenData = {
reportCode: this.$route.query.reportCode,
dashboard: {
title: this.dashboard.title,
width: this.dashboard.width,
height: this.dashboard.height,
backgroundColor: this.dashboard.backgroundColor,
backgroundImage: this.dashboard.backgroundImage
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
},
//
viewScreen() {
let routeUrl = this.$router.resolve({
path: "/screen/preview",
query: { reportCode: this.$route.query.reportCode }
});
window.open(routeUrl.href, "_blank");
},
//
handleRedo() {
const record = this.revoke.redo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUndo() {
const record = this.revoke.undo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.initEchartData();
if (response.code == "200") {
this.$message({
message: "导入成功!",
type: "success"
});
} else {
this.$message({
message: response.message,
type: "error"
});
}
},
//
handleError(err) {
this.$message({
message: "上传失败!",
type: "error"
});
},
//
async exportDashboard(val) {
console.log(val);
const fileName = this.$route.query.reportCode + ".zip";
const param = {
reportCode: this.$route.query.reportCode,
showDataSet: val
};
exportDashboard(param).then(res => {
const that = this;
const type = res.type;
if (type == "application/json") {
let reader = new FileReader();
reader.readAsText(res, "utf-8");
reader.onload = function() {
const data = JSON.parse(reader.result);
that.$message.error(data.message);
};
return;
}
const blob = new Blob([res], { type: "application/octet-stream" });
if (window.navigator.msSaveOrOpenBlob) {
//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName); //
} else {
const link = document.createElement("a"); //a
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
});
},
//
deletelayer() {
this.widgets.splice(this.rightClickIndex, 1);
},
//
copylayer() {
const obj = this.deepClone(this.widgets[this.rightClickIndex]);
this.widgets.splice(this.widgets.length, 0, obj);
},
//
istopLayer() {
if (this.rightClickIndex + 1 < this.widgets.length) {
const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
this.widgets.push(temp);
}
},
//
setlowLayer() {
if (this.rightClickIndex != 0) {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
//
moveupLayer() {
if (this.rightClickIndex != 0) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex - 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.push(this.widgets.shift());
}
},
//
movedownLayer() {
if (this.rightClickIndex != this.widgets.length - 1) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex + 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
}
}
};

@ -0,0 +1,725 @@
<template>
<div class="layout">
<div class="layout-container">
<!-- 图表 -->
<el-tabs class="layout-left" type="border-card">
<el-tab-pane>
<span slot="label"><i class="el-icon-date icon"></i>工具栏</span>
<div class="chart-type">
<el-tabs class="type-left" tab-position="left">
<el-tab-pane
v-for="(item, index) in widgetTools"
:key="index"
:label="item.name"
>
<draggable
v-for="(it, idx) in item.list"
:key="idx"
@end="evt => widgetOnDragged(evt, it.code)"
>
<div class="tools-item">
<span class="tools-item-icon">
<i class="iconfont" :class="it.icon"></i>
</span>
<span class="tools-item-text">{{ it.label }}</span>
</div>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label" class="icon"
><i class="el-icon-date icon"></i>图层</span
>
<div
v-for="(item, index) in layerWidget"
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
</span>
<span class="tools-item-text">{{ item.label }}</span>
</div>
</el-tab-pane>
</el-tabs>
<div
class="layout-middle"
:style="{
width: middleWidth + 'px',
height: middleHeight + 'px'
}"
>
<!-- 操作栏 -->
<div class="layout-bar">
<div class="bar-item" @click="saveData">
<i class="iconfont iconsave"></i>保存
</div>
<div class="bar-item" @click="viewScreen">
<i class="iconfont iconyulan"></i>预览
</div>
<div class="bar-item" @click="handleUndo">
<i class="iconfont iconundo"></i>撤销
</div>
<div class="bar-item" @click="handleRedo">
<i class="iconfont iconhuifubeifen"></i>恢复
</div>
<div class="bar-item">
<el-upload
class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1"
>
<i class="iconfont icondaoru"></i>
</el-upload>
导入
</div>
<div class="bar-item">
<i class="iconfont icondaochu"></i>
<el-dropdown @command="exportDashboard">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1"
>导出(包含数据集)</el-dropdown-item
>
<el-dropdown-item command="0"
>导出(不包含数据集)</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<!-- 设计器 -->
<div
class="workbench-container"
:style="{
width: bigscreenWidthInWorkbench + 'px',
height: bigscreenHeightInWorkbench + 'px'
}"
@mousedown="handleMouseDown"
>
<vue-ruler-tool
v-model="dashboard.presetLine"
class="vueRuler"
:step-length="50"
:parent="true"
:position="'relative'"
:is-scale-revise="true"
:visible.sync="dashboard.presetLineVisible"
>
<div
id="workbench"
class="workbench"
:style="{
transform: workbenchTransform,
width: bigscreenWidth + 'px',
height: bigscreenHeight + 'px',
'background-color': dashboard.backgroundColor,
'background-image': 'url(' + dashboard.backgroundImage + ')',
'background-position': '0% 0%',
'background-size': '100% 100%',
'background-repeat': 'initial',
'background-attachment': 'initial',
'background-origin': 'initial',
'background-clip': 'initial'
}"
@click.self="setOptionsOnClickScreen"
>
<div v-if="grade" class="bg-grid"></div>
<widget
ref="widgets"
v-for="(widget, index) in widgets"
:key="index"
v-model="widget.value"
:index="index"
:step="step"
:type="widget.type"
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mouseup.prevent.native="widgetsMouseup"
/>
</div>
</vue-ruler-tool>
</div>
</div>
<!-- 设置 -->
<div class="layout-right">
<el-tabs v-model="activeName" type="border-card" :stretch="true">
<el-tab-pane
v-if="
isNotNull(widgetOptions.setup) ||
isNotNull(widgetOptions.collapse)
"
name="first"
label="配置"
>
<dynamicForm
ref="formData"
:options="widgetOptions.setup"
@onChanged="val => widgetValueChanged('setup', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.data)"
name="second"
label="数据"
>
<dynamicForm
ref="formData"
:options="widgetOptions.data"
@onChanged="val => widgetValueChanged('data', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.position)"
name="third"
label="坐标"
>
<dynamicForm
ref="formData"
:options="widgetOptions.position"
@onChanged="val => widgetValueChanged('position', val)"
/>
</el-tab-pane>
</el-tabs>
</div>
</div>
<content-menu
:visible.sync="visibleContentMenu"
:style-obj="styleObj"
@deletelayer="deletelayer"
@copylayer="copylayer"
@istopLayer="istopLayer"
@setlowLayer="setlowLayer"
@moveupLayer="moveupLayer"
@movedownLayer="movedownLayer"
/>
</div>
</template>
<script>
import {
insertDashboard,
detailDashboard,
importDashboard,
exportDashboard
} from "@/api/bigscreen";
import {
swapArr,
setDefaultValue,
handleDefaultValue,
getPXUnderScale,
handleInitEchartsData,
handleBigScreen,
handlerLayerWidget
} from "./util/screen";
import { screenConfig } from "./config/texts/screenConfig.js";
import { widgetTools, getToolByCode } from "./config/index.js";
import VueRulerTool from "vue-ruler-tool"; //
import widget from "./widget/index.vue";
import dynamicForm from "./components/dynamicForm.vue";
import draggable from "vuedraggable";
import contentMenu from "./components/contentMenu";
import { getToken } from "@/utils/auth";
import { Revoke } from "@/utils/revoke"; // 2022-02-22
export default {
components: {
VueRulerTool,
widget,
dynamicForm,
draggable,
contentMenu
},
data() {
return {
uploadUrl:
process.env.BASE_API +
"/reportDashboard/import/" +
this.$route.query.reportCode,
grade: false,
layerWidget: [],
widgetTools: widgetTools, // js
widthLeftForTools: 200, //
widthLeftForToolsHideButton: 15, //
widthLeftForOptions: 300, //
widthPaddingTools: 18,
toolIsShow: true, //
bigscreenWidth: 1920, //
bigscreenHeight: 1080,
revoke: null, // 2022-02-22
// gaea_report_dashboard
dashboard: {
id: null,
title: "", //
width: 1920, //
height: 1080, //
backgroundColor: "", //
backgroundImage: "", //
refreshSeconds: null, //
presetLine: [], // 线
presetLineVisible: true // 线
},
//
screenCode: "",
//
widgets: [
{
// typevaluegaea_report_dashboard_widget
type: "widget-text",
value: {
setup: {},
data: {},
position: {
width: 100,
height: 100,
left: 0,
top: 0,
zIndex: 0
}
},
// optionstools
options: []
}
], //
//
widgetIndex: 0,
//
widgetOptions: {
setup: [], //
data: [], //
position: [] //
},
flagWidgetClickStopPropagation: false, // click
styleObj: {
left: 0,
top: 0
},
visibleContentMenu: false,
rightClickIndex: -1,
activeName: "first"
};
},
computed: {
step() {
return Number(100 / (this.bigscreenScaleInWorkbench * 100));
},
headers() {
return {
Authorization: getToken() // token
};
},
//
middleWidth() {
let widthLeftAndRight = 0;
if (this.toolIsShow) {
widthLeftAndRight += this.widthLeftForTools; //
}
widthLeftAndRight += this.widthLeftForToolsHideButton; //
widthLeftAndRight += this.widthLeftForOptions; //
let middleWidth = this.bodyWidth - widthLeftAndRight;
return middleWidth;
},
middleHeight() {
return this.bodyHeight;
},
//
bigscreenScaleInWorkbench() {
let widthScale =
(this.middleWidth - this.widthPaddingTools) / this.bigscreenWidth;
let heightScale =
(this.middleHeight - this.widthPaddingTools) / this.bigscreenHeight;
return Math.min(widthScale, heightScale);
},
workbenchTransform() {
return `scale(${this.bigscreenScaleInWorkbench}, ${
this.bigscreenScaleInWorkbench
})`;
},
//
bigscreenWidthInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenWidth) +
this.widthPaddingTools
);
},
bigscreenHeightInWorkbench() {
return (
getPXUnderScale(this.bigscreenScaleInWorkbench, this.bigscreenHeight) +
this.widthPaddingTools
);
}
},
watch: {
widgets: {
handler(val) {
this.layerWidget = handlerLayerWidget(val, getToolByCode);
//
this.$nextTick(() => {
this.revoke.push(this.widgets);
});
},
deep: true
}
},
created() {
/* 以下是记录历史的 */
this.revoke = new Revoke();
},
mounted() {
this.initScreen();
//
// this.initEchartData();
this.widgets = [];
window.addEventListener("mouseup", () => {
this.grade = false;
});
},
methods: {
//
initScreen() {
this.widgetOptions = screenConfig["options"];
},
// echrats
async initEchartData() {
const reportCode = this.$route.query.reportCode;
const { code, data } = await detailDashboard(reportCode);
if (code != 200) return;
const processData = handleInitEchartsData(data, getToolByCode);
const screenData = handleBigScreen(
data.dashboard,
getToolByCode,
this.setOptionsOnClickScreen
);
this.widgets = processData;
this.dashboard = screenData;
this.bigscreenWidth = this.dashboard.width;
this.bigscreenHeight = this.dashboard.height;
},
//
widgetOnDragged(evt, widgetCode) {
let widgetType = widgetCode;
//
let eventX = evt.originalEvent.clientX; // x
let eventY = evt.originalEvent.clientY; // y
let workbenchPosition = this.getDomTopLeftById("workbench");
let widgetTopInWorkbench = eventY - workbenchPosition.top;
let widgetLeftInWorkbench = eventX - workbenchPosition.left;
// x y
let x = widgetLeftInWorkbench / this.bigscreenScaleInWorkbench;
let y = widgetTopInWorkbench / this.bigscreenScaleInWorkbench;
//
let tool = getToolByCode(widgetType);
let widgetJson = {
type: widgetType,
value: {
setup: {},
data: {},
position: {
width: 0,
height: 0,
left: 0,
top: 0,
zIndex: 0
}
},
options: tool.options
};
//
const widgetJsonValue = handleDefaultValue(widgetJson);
//20220222
widgetJsonValue.value.position.left =
x - widgetJsonValue.value.position.width / 2;
widgetJsonValue.value.position.top =
y - widgetJsonValue.value.position.height / 2;
//
this.widgets.push(this.deepClone(widgetJsonValue));
//
this.setOptionsOnClickWidget(this.widgets.length - 1);
},
//
setOptionsOnClickScreen() {
this.screenCode = "screen";
//
this.activeName = "first";
this.widgetOptions = screenConfig["options"];
},
layerClick(index) {
this.widgetIndex = index;
this.widgetsClick(index);
},
//
setOptionsOnClickWidget(obj) {
console.log(obj);
this.screenCode = "";
if (typeof obj == "number") {
this.widgetOptions = this.deepClone(this.widgets[obj]["options"]);
return;
}
if (obj.index < 0 || obj.index >= this.widgets.length) {
return;
}
this.widgetIndex = obj.index;
this.widgets[obj.index].value.position = obj;
this.widgets[obj.index].options.position.forEach(el => {
for (const key in obj) {
if (el.name == key) {
el.value = obj[key];
}
}
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
},
widgetsClick(index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
this.$refs.widgets[i].$refs.draggable.setActive(true);
} else {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
}
this.setOptionsOnClickWidget(index);
this.grade = true;
},
widgetsMouseup(e) {
this.grade = false;
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
},
//
widgetValueChanged(key, val) {
if (this.screenCode == "screen") {
let newSetup = new Array();
this.dashboard = this.deepClone(val);
if (this.bigscreenWidth != this.dashboard.width) {
this.bigscreenWidth = this.dashboard.width;
}
if (this.bigscreenHeight != this.dashboard.height) {
this.bigscreenHeight = this.dashboard.height;
}
this.widgetOptions.setup.forEach(el => {
if (el.name == "width") {
el.value = this.bigscreenWidth;
} else if (el.name == "height") {
el.value = this.bigscreenHeight;
}
newSetup.push(el);
});
this.widgetOptions.setup = newSetup;
} else {
for (let i = 0; i < this.widgets.length; i++) {
if (this.widgetIndex == i) {
this.widgets[i].value[key] = this.deepClone(val);
setDefaultValue(this.widgets[i].options[key], val);
}
}
}
},
rightClick(event, index) {
this.rightClickIndex = index;
const left = event.clientX;
const top = event.clientY;
if (left || top) {
this.styleObj = {
left: left + "px",
top: top + "px",
display: "block"
};
}
this.visibleContentMenu = true;
return false;
},
datadragEnd(evt) {
evt.preventDefault();
this.widgets = swapArr(this.widgets, evt.oldIndex, evt.newIndex);
},
//
async saveData() {
if (!this.widgets || this.widgets.length == 0) {
this.$message.error("请添加组件");
return;
}
const screenData = {
reportCode: this.$route.query.reportCode,
dashboard: {
title: this.dashboard.title,
width: this.dashboard.width,
height: this.dashboard.height,
backgroundColor: this.dashboard.backgroundColor,
backgroundImage: this.dashboard.backgroundImage
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
},
//
viewScreen() {
let routeUrl = this.$router.resolve({
path: "/screen/preview",
query: { reportCode: this.$route.query.reportCode }
});
window.open(routeUrl.href, "_blank");
},
//
handleRedo() {
const record = this.revoke.redo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUndo() {
const record = this.revoke.undo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.initEchartData();
if (response.code == "200") {
this.$message({
message: "导入成功!",
type: "success"
});
} else {
this.$message({
message: response.message,
type: "error"
});
}
},
//
handleError(err) {
this.$message({
message: "上传失败!",
type: "error"
});
},
//
async exportDashboard(val) {
console.log(val);
const fileName = this.$route.query.reportCode + ".zip";
const param = {
reportCode: this.$route.query.reportCode,
showDataSet: val
};
exportDashboard(param).then(res => {
const that = this;
const type = res.type;
if (type == "application/json") {
let reader = new FileReader();
reader.readAsText(res, "utf-8");
reader.onload = function() {
const data = JSON.parse(reader.result);
that.$message.error(data.message);
};
return;
}
const blob = new Blob([res], {
type: "application/octet-stream"
});
if (window.navigator.msSaveOrOpenBlob) {
//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName); //
} else {
const link = document.createElement("a"); //a
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
});
},
//
deletelayer() {
this.widgets.splice(this.rightClickIndex, 1);
},
//
copylayer() {
const obj = this.deepClone(this.widgets[this.rightClickIndex]);
this.widgets.splice(this.widgets.length, 0, obj);
},
//
istopLayer() {
if (this.rightClickIndex + 1 < this.widgets.length) {
const temp = this.widgets.splice(this.rightClickIndex, 1)[0];
this.widgets.push(temp);
}
},
//
setlowLayer() {
if (this.rightClickIndex != 0) {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
},
//
moveupLayer() {
if (this.rightClickIndex != 0) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex - 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.push(this.widgets.shift());
}
},
//
movedownLayer() {
if (this.rightClickIndex != this.widgets.length - 1) {
this.widgets[this.rightClickIndex] = this.widgets.splice(
this.rightClickIndex + 1,
1,
this.widgets[this.rightClickIndex]
)[0];
} else {
this.widgets.unshift(this.widgets.splice(this.rightClickIndex, 1)[0]);
}
}
}
};
</script>
<style scoped lang="scss">
@import "../../assets/styles/screenDesigner.scss";
</style>

@ -0,0 +1,144 @@
<!--
* @Descripttion: 大屏左侧工具栏
* @version:
* @Author: qianlishi
* @Date: 2022-05-12 11:05:21
* @LastEditors: qianlishi
* @LastEditTime: 2022-06-17 17:26:22
-->
<template>
<el-tabs class="layout-left" type="border-card">
<el-tab-pane>
<span slot="label"><i class="el-icon-date icon"></i>工具栏</span>
<div class="chart-type">
<el-tabs class="type-left" tab-position="left">
<el-tab-pane
v-for="(item, index) in widgetTools"
:key="index"
:label="item.name"
>
<draggable
v-for="(it, idx) in item.list"
:key="idx"
@end="evt => widgetOnDragged(evt, it.code)"
>
<div class="tools-item" v-if="it.code != 'screen'">
<span class="tools-item-icon">
<i class="iconfont" :class="it.icon"></i>
</span>
<span class="tools-item-text">{{ it.label }}</span>
</div>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
</el-tab-pane>
<el-tab-pane>
<span slot="label" class="icon"
><i class="el-icon-date icon"></i>图层</span
>
<div
v-for="(item, index) in layerWidget"
:key="'item' + index"
class="tools-item"
:class="widgetIndex == index ? 'is-active' : ''"
@click="layerClick(index)"
>
<span class="tools-item-icon">
<i class="iconfont" :class="item.icon"></i>
</span>
<span class="tools-item-text">{{ item.label }}</span>
</div>
</el-tab-pane>
</el-tabs>
</template>
<script>
import draggable from "vuedraggable";
export default {
components: {
draggable
},
props: {
widgetIndex: Number,
widgetTools: Array,
layerWidget: Array
},
methods: {
widgetOnDragged(evt, code) {
this.$emit("widgetOnDragged", evt, code);
},
layerClick(index) {
this.$emit("layerClick", index);
}
}
};
</script>
<style lang="scss" scoped>
/deep/.el-dropdown-menu__item {
max-width: none;
}
.layout-left {
width: 200px;
background: #242a30;
overflow-x: hidden;
overflow-y: auto;
.chart-type {
display: flex;
flex-direction: row;
overflow: hidden;
.type-left {
width: 100%;
height: calc(100vh - 80px);
text-align: center;
/deep/.el-tabs__header {
width: 30%;
margin-right: 0;
.el-tabs__nav-wrap {
&::after {
background: transparent;
}
.el-tabs__item {
text-align: center;
width: 100% !important;
color: #fff;
padding: 0;
}
}
}
/deep/.el-tabs__content {
width: 70%;
}
}
}
//
.tools-item {
display: flex;
position: relative;
width: 100%;
height: 48px;
align-items: center;
-webkit-box-align: center;
padding: 0 6px;
cursor: pointer;
font-size: 12px;
margin-bottom: 1px;
.tools-item-icon {
color: #409eff;
margin-right: 10px;
width: 53px;
height: 30px;
line-height: 30px;
text-align: center;
display: block;
border: 1px solid #3a4659;
background: #282a30;
}
.tools-item-text {
}
}
/deep/.el-tabs__content {
padding: 0;
}
}
</style>

@ -0,0 +1,260 @@
<!--
* @Descripttion: 大屏设计器
* @version:
* @Author: qianlishi
* @Date: 2022-05-12 11:05:48
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-21 14:47:00
-->
<template>
<div
class="workbench-container"
:style="{
width: bigscreenWidthInWorkbench + 'px',
height: bigscreenHeightInWorkbench + 'px'
}"
@mousedown="handleMouseDown"
>
<vue-ruler-tool
v-model="dashboard.presetLine"
class="vueRuler"
:step-length="50"
:parent="true"
:position="'relative'"
:is-scale-revise="true"
:visible.sync="dashboard.presetLineVisible"
>
<div
id="workbench"
class="workbench"
:style="{
transform: workbenchTransform,
width: bigscreenWidth + 'px',
height: bigscreenHeight + 'px',
'background-color': dashboard.backgroundColor,
'background-image': 'url(' + dashboard.backgroundImage + ')',
'background-position': '0% 0%',
'background-size': '100% 100%',
'background-repeat': 'initial',
'background-attachment': 'initial',
'background-origin': 'initial',
'background-clip': 'initial'
}"
@click.self="setOptionsOnClickScreen"
>
<div v-if="grade" class="bg-grid"></div>
<widget
ref="widgets"
v-for="(widget, index) in widgets"
:key="index"
v-model="widget.value"
:index="index"
:step="step"
:type="widget.type"
:bigscreen="{ bigscreenWidth, bigscreenHeight }"
@onActivated="setOptionsOnClickWidget"
@contextmenu.prevent.native="rightClick($event, index)"
@mousedown.prevent.native="widgetsClick(index)"
@mouseup.prevent.native="widgetsMouseup"
/>
</div>
</vue-ruler-tool>
<!-- 右键 -->
<content-menu
:visible.sync="visibleContentMenu"
:style-obj="styleObj"
:widgets="widgets"
:rightClickIndex="rightClickIndex"
/>
</div>
</template>
<script>
import {
swapArr,
setDefaultValue,
handleDefaultValue,
getPXUnderScale,
handleInitEchartsData,
handleBigScreen,
handlerLayerWidget
} from "../util/screen";
import { screenConfig } from "../config/texts/screenConfig.js";
import VueRulerTool from "vue-ruler-tool";
import widget from "../widget/index.vue";
import contentMenu from "../components/contentMenu";
export default {
components: {
VueRulerTool,
widget,
contentMenu
},
props: {
dashboard: Object,
middleWidth: Number,
middleHeight: Number,
widgets: Array
},
data() {
return {
grade: false,
bigscreenWidth: 1920, //
bigscreenHeight: 1080,
widgetOptions: {},
styleObj: {
left: 0,
top: 0
},
rightClickIndex: -1,
//
visibleContentMenu: false
};
},
computed: {
step() {
return Number(100 / (this.bigscreenScaleInWorkbench * 100));
},
//
bigscreenScaleInWorkbench() {
let widthScale = this.middleWidth / this.bigscreenWidth;
let heightScale = this.middleHeight / this.bigscreenHeight;
return Math.min(widthScale, heightScale);
},
workbenchTransform() {
return `scale(${this.bigscreenScaleInWorkbench}, ${
this.bigscreenScaleInWorkbench
})`;
},
//
bigscreenWidthInWorkbench() {
return getPXUnderScale(
this.bigscreenScaleInWorkbench,
this.bigscreenWidth
);
},
bigscreenHeightInWorkbench() {
return getPXUnderScale(
this.bigscreenScaleInWorkbench,
this.bigscreenHeight
);
}
},
mounted() {
this.loadOption();
},
methods: {
//
loadOption() {
this.widgetOptions = screenConfig["options"];
this.$emit("change", this.widgetOptions);
},
handleMouseDown() {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
},
rightClick(event, index) {
this.rightClickIndex = index;
const left = event.clientX;
const top = event.clientY;
if (left || top) {
this.styleObj = {
left: left + "px",
top: top + "px",
display: "block"
};
}
this.visibleContentMenu = true;
return false;
},
//
setOptionsOnClickScreen() {
this.screenCode = "screen";
//
this.activeName = "first";
this.widgetOptions = screenConfig["options"];
this.$emit("change", this.widgetOptions);
},
//
setOptionsOnClickWidget(obj) {
console.log(obj);
this.screenCode = "";
if (typeof obj == "number") {
this.widgetOptions = this.deepClone(this.widgets[obj]["options"]);
return;
}
if (obj.index < 0 || obj.index >= this.widgets.length) {
return;
}
this.widgetIndex = obj.index;
this.widgets[obj.index].value.position = obj;
this.widgets[obj.index].options.position.forEach(el => {
for (const key in obj) {
if (el.name == key) {
el.value = obj[key];
}
}
});
this.widgetOptions = this.deepClone(this.widgets[obj.index]["options"]);
this.$emit("change", this.widgetOptions);
},
widgetsClick(index) {
const draggableArr = this.$refs.widgets;
for (let i = 0; i < draggableArr.length; i++) {
if (i == index) {
this.$refs.widgets[i].$refs.draggable.setActive(true);
} else {
this.$refs.widgets[i].$refs.draggable.setActive(false);
}
}
this.setOptionsOnClickWidget(index);
this.grade = true;
},
widgetsMouseup(e) {
this.grade = false;
}
}
};
</script>
<style lang="scss" scoped>
.workbench-container {
position: relative;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
.vueRuler {
width: 100%;
padding: 18px 0px 0px 18px;
}
.workbench {
background-color: #1e1e1e;
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
margin: 0;
padding: 0;
}
.bg-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: 30px 30px, 30px 30px;
background-image: linear-gradient(hsla(0, 0%, 100%, 0.1) 1px, transparent 0),
linear-gradient(90deg, hsla(0, 0%, 100%, 0.1) 1px, transparent 0);
// z-index: 2;
}
}
</style>

@ -0,0 +1,70 @@
<!--
* @Descripttion: 大屏右侧配置
* @version:
* @Author: qianlishi
* @Date: 2022-05-12 11:05:54
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-14 11:52:30
-->
<template>
<div class="layout-right">
<el-tabs v-model="activeName" type="border-card" :stretch="true">
<el-tab-pane
v-if="
isNotNull(widgetOptions.setup) || isNotNull(widgetOptions.collapse)
"
name="first"
label="配置"
>
<dynamicForm
ref="formData"
:options="widgetOptions.setup"
@onChanged="val => widgetValueChanged('setup', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.data)"
name="second"
label="数据"
>
<dynamicForm
ref="formData"
:options="widgetOptions.data"
@onChanged="val => widgetValueChanged('data', val)"
/>
</el-tab-pane>
<el-tab-pane
v-if="isNotNull(widgetOptions.position)"
name="third"
label="坐标"
>
<dynamicForm
ref="formData"
:options="widgetOptions.position"
@onChanged="val => widgetValueChanged('position', val)"
/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import dynamicForm from "../components/dynamicForm.vue";
export default {
components: {
dynamicForm
},
data() {
return {
activeName: "first"
};
},
props: {
widgetOptions: Object
},
methods: {
widgetValueChanged(type, val) {
this.$emit("widgetValueChanged", type, val);
}
}
};
</script>

@ -0,0 +1,229 @@
<!--
* @Descripttion: 大屏顶部操操按钮
* @version:
* @Author: qianlishi
* @Date: 2022-05-14 11:54:07
* @LastEditors: qianlishi
* @LastEditTime: 2022-05-14 12:52:58
-->
<template>
<!-- 操作栏 -->
<div class="layout-bar">
<div class="bar-item" @click="saveData">
<i class="iconfont iconsave"></i>保存
</div>
<div class="bar-item" @click="viewScreen">
<i class="iconfont iconyulan"></i>预览
</div>
<div class="bar-item" @click="handleUndo">
<i class="iconfont iconundo"></i>撤销
</div>
<div class="bar-item" @click="handleRedo">
<i class="iconfont iconhuifubeifen"></i>恢复
</div>
<div class="bar-item">
<el-upload
class="el-upload"
ref="upload"
:action="uploadUrl"
:headers="headers"
accept=".zip"
:on-success="handleUpload"
:on-error="handleError"
:show-file-list="false"
:limit="1"
>
<i class="iconfont icondaoru"></i>
</el-upload>
导入
</div>
<div class="bar-item">
<i class="iconfont icondaochu"></i>
<el-dropdown @command="exportDashboard">
<span class="el-dropdown-link">
导出<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1">导出(包含数据集)</el-dropdown-item>
<el-dropdown-item command="0">导出(不包含数据集)</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { insertDashboard, exportDashboard } from "@/api/bigscreen";
import { Revoke } from "../util/revoke";
import { getToken } from "@/utils/auth";
export default {
props: {
dashboard: Object,
widgets: Array
},
data() {
return {
uploadUrl:
process.env.BASE_API +
"/reportDashboard/import/" +
this.$route.query.reportCode,
revoke: null
};
},
computed: {
headers() {
return {
Authorization: getToken()
};
}
},
watch: {
widgets: {
handler(val) {
//
this.$nextTick(() => {
this.revoke.push(this.widgets);
});
},
deep: true
}
},
created() {
/* 以下是记录历史的 */
this.revoke = new Revoke();
},
methods: {
//
async saveData() {
if (!this.widgets || this.widgets.length == 0) {
return this.$message.error("请添加组件");
}
const screenData = {
reportCode: this.$route.query.reportCode,
dashboard: {
title: this.dashboard.title,
width: this.dashboard.width,
height: this.dashboard.height,
backgroundColor: this.dashboard.backgroundColor,
backgroundImage: this.dashboard.backgroundImage
},
widgets: this.widgets
};
const { code, data } = await insertDashboard(screenData);
if (code == "200") {
this.$message.success("保存成功!");
}
},
//
viewScreen() {
let routeUrl = this.$router.resolve({
path: "/screen/preview",
query: { reportCode: this.$route.query.reportCode }
});
window.open(routeUrl.href, "_blank");
},
//
handleUndo() {
const record = this.revoke.undo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleRedo() {
const record = this.revoke.redo();
if (!record) {
return false;
}
this.widgets = record;
},
//
handleError(err) {
this.$message({
message: "上传失败!",
type: "error"
});
},
//
handleUpload(response, file, fileList) {
//el-upload
this.$refs.upload.clearFiles();
//
this.irefresh();
if (response.code == "200") {
this.$message({
message: "导入成功!",
type: "success"
});
} else {
this.$message({
message: response.message,
type: "error"
});
}
},
//
async exportDashboard(val) {
const fileName = this.$route.query.reportCode + ".zip";
const param = {
reportCode: this.$route.query.reportCode,
showDataSet: val
};
exportDashboard(param).then(res => {
const that = this;
const type = res.type;
if (type == "application/json") {
let reader = new FileReader();
reader.readAsText(res, "utf-8");
reader.onload = function() {
const data = JSON.parse(reader.result);
that.$message.error(data.message);
};
return;
}
const blob = new Blob([res], {
type: "application/octet-stream"
});
if (window.navigator.msSaveOrOpenBlob) {
//msSaveOrOpenBlobbool
navigator.msSaveBlob(blob, fileName); //
} else {
const link = document.createElement("a"); //a
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
});
},
refresh() {
this.$emit("refresh");
}
}
};
</script>
<style scoped lang="scss">
.layout-bar {
height: 40px;
line-height: 40px;
font-size: 12px;
padding: 0 10px;
display: flex;
flex-direction: row;
overflow: hidden;
.bar-item {
margin-right: 20px;
cursor: pointer;
.iconfont {
font-size: 12px;
margin-right: 4px;
}
.el-dropdown-link {
color: #fff;
cursor: pointer;
}
}
}
</style>

@ -13,6 +13,10 @@
│ │ ├── pieCharts(文件夹) (饼图)
│ │ ├── texts(文件夹) (文本、滚动文本、超链接、当前时间、图片、视频、表格、内联框架)
│ │ ├── wordcloudCharts(文件夹) (词云图)
│ ├── layout(文件夹)
│ │ ├── left.vue 大屏左侧工具栏
│ │ ├── middle.vue 大屏中间配置
│ │ ├── right.vue 大屏右侧配置
│ ├── util(文件夹) 公共 js
│ ├── widget(文件夹) 图表组件
│ │ ├── barCharts(文件夹) (柱状图)

@ -0,0 +1,92 @@
export class Revoke {
// 历史记录
recordList = [];
// 撤销记录,用于重做
redoList = [];
// 当前记录用currentRecord变量暂时存储当用户修改时再存放到recordList
currentRecord = null;
// 上次插入数据时间
time = 0;
/**
* @description: 插入历史记录
* @param {object}record
* @return {boolean}
*/
push(record) {
const nowTime = Date.now();
// 防止添加重复的时间当添加间隔小于100ms时则替换当前记录并取消执行添加
if (this.time + 100 > nowTime) {
this.currentRecord = JSON.stringify(record);
return false;
}
this.time = nowTime;
// 判断之前是否已经存在currentRecord记录有则存储到recordList
if (this.currentRecord) {
this.recordList.push(this.currentRecord);
//(清空记录)增加记录后则应该清空重做记录
//splice() 方法向/从数组添加/删除项目,并返回删除的项目。
this.redoList.splice(0, this.redoList.length);
}
// 将json转成字符串存储
this.currentRecord = JSON.stringify(record);
// 最多存储2000条记录超过2000条记录则删除之前的记录
if (this.length > 2000) {
//unshift() 方法将新项添加到数组的开头,并返回新的长度。
this.recordList.unshift();
}
return true;
}
/**
* @description: 撤销操作
* @param {*}
* @return {object}
*/
undo() {
// 没有记录时,返回false
// 新建的recordList里面不知为什么会存在一条记录未找到原因所以就判断长度为1时就不能撤销了。
if (this.recordList.length === 1 ) {
return false;
}
//pop() 方法用于删除并返回数组的最后一个元素。
const record = this.recordList.pop();
// 将当前记录添加到重做记录里面
if (this.currentRecord) {
this.redoList.push(this.currentRecord);
}
// 丢弃当前记录,防止重复添加
this.currentRecord = null;
//返回撤销的记录
return JSON.parse(record);
}
/**
* @description: 重做操作
* @param {*}
* @return {*}
*/
redo() {
// 没有重做记录时,返回false
if (this.redoList.length === 0) {
return false;
}
//pop() 方法用于删除并返回数组的最后一个元素。
const record = this.redoList.pop();
// 添加到重做记录里面
if (this.currentRecord) {
this.recordList.push(this.currentRecord);
}
// 丢弃当前记录,防止重复添加
this.currentRecord = null;
return JSON.parse(record);
}
}

@ -15,6 +15,7 @@ export const handlerLayerWidget = (val, getToolByCode) => {
}
export const handleBigScreen = (data, getToolByCode, callBcak) => {
console.log('aa', getToolByCode("screen"))
const optionScreen = getToolByCode("screen").options;
const setup = optionScreen.setup;
for (const key in data) {

Loading…
Cancel
Save