浏览代码

!84 update 0.9.8
Merge pull request !84 from Foming/dev

Foming 3 年之前
父节点
当前提交
274e87ca4b
共有 53 个文件被更改,包括 2580 次插入971 次删除
  1. 1 1
      README.md
  2. 1 1
      doc/docs/.vuepress/config.js
  3. 1 0
      doc/docs/guide/datasource.md
  4. 8 8
      doc/docs/guide/excel.md
  5. 5 3
      doc/docs/guide/question.md
  6. 3 3
      doc/docs/guide/quicklyDistribution.md
  7. 0 0
      doc/docs/picture/excel/img.png
  8. 0 0
      doc/docs/picture/excel/img_1.png
  9. 0 0
      doc/docs/picture/excel/img_2.png
  10. 0 0
      doc/docs/picture/excel/img_3.png
  11. 0 0
      doc/docs/picture/excel/img_4.png
  12. 0 0
      doc/docs/picture/excel/img_5.png
  13. 3 0
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportexcel/service/impl/ReportExcelServiceImpl.java
  14. 7 0
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/controller/ReportShareController.java
  15. 4 0
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/controller/dto/ReportShareDto.java
  16. 19 5
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/controller/param/ReportShareParam.java
  17. 3 1
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/dao/entity/ReportShare.java
  18. 6 0
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/service/ReportShareService.java
  19. 46 5
      report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/service/impl/ReportShareServiceImpl.java
  20. 17 0
      report-core/src/main/java/com/anjiplus/template/gaea/business/util/DateUtil.java
  21. 9 0
      report-core/src/main/resources/db/migration/V1.0.21__update_widget_column.sql
  22. 10 0
      report-core/src/main/resources/db/migration/V1.0.22__add_report_share.sql
  23. 1 1
      report-ui/config/dev.env.js
  24. 16 8
      report-ui/src/api/reportShare.js
  25. 二进制
      report-ui/src/assets/images/apache.png
  26. 2 131
      report-ui/src/assets/styles/screenDesigner.scss
  27. 210 193
      report-ui/src/router/index.js
  28. 1 1
      report-ui/src/utils/china.js
  29. 9 2
      report-ui/src/views/bigScreenReport/components/share.vue
  30. 5 1
      report-ui/src/views/bigScreenReport/index.vue
  31. 10 11
      report-ui/src/views/bigscreenDesigner/designer/tools/configure/widget-more-bar-line.js
  32. 6 12
      report-ui/src/views/bigscreenDesigner/designer/widget/bar/widgetMoreBarLineChart.vue
  33. 15 3
      report-ui/src/views/excelreport/components/share.vue
  34. 85 0
      report-ui/src/views/excelreport/el/index.vue
  35. 4 5
      report-ui/src/views/excelreport/index.vue
  36. 72 18
      report-ui/src/views/layout/components/Navbar.vue
  37. 1 1
      report-ui/src/views/layout/components/Sidebar/index.vue
  38. 85 7
      report-ui/src/views/login.vue
  39. 9 2
      report-ui/src/views/reportManage/components/share.vue
  40. 3 5
      report-ui/src/views/reportManage/index.vue
  41. 259 0
      report-ui/src/views/reportShare/index.vue
  42. 32 7
      report-ui/src/views/screenDesigner/components/contentMenu.vue
  43. 2 0
      report-ui/src/views/screenDesigner/config/configs.js
  44. 8 0
      report-ui/src/views/screenDesigner/config/index.js
  45. 77 536
      report-ui/src/views/screenDesigner/index.vue
  46. 725 0
      report-ui/src/views/screenDesigner/index备份.vue
  47. 144 0
      report-ui/src/views/screenDesigner/layout/leftMenu.vue
  48. 260 0
      report-ui/src/views/screenDesigner/layout/middleScreen.vue
  49. 70 0
      report-ui/src/views/screenDesigner/layout/rightConfig.vue
  50. 229 0
      report-ui/src/views/screenDesigner/layout/topBar.vue
  51. 4 0
      report-ui/src/views/screenDesigner/node.md
  52. 92 0
      report-ui/src/views/screenDesigner/util/revoke.js
  53. 1 0
      report-ui/src/views/screenDesigner/util/screen.js

+ 1 - 1
README.md

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

+ 1 - 1
doc/docs/.vuepress/config.js

@@ -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'},
                     ]

+ 1 - 0
doc/docs/guide/datasource.md

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

+ 8 - 8
doc/docs/guide/execl.md → doc/docs/guide/excel.md

@@ -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>

+ 5 - 3
doc/docs/guide/question.md

@@ -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>
 

+ 3 - 3
doc/docs/guide/quicklyDistribution.md

@@ -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
 ```
 

+ 0 - 0
doc/docs/picture/execl/img.png → doc/docs/picture/excel/img.png


+ 0 - 0
doc/docs/picture/execl/img_1.png → doc/docs/picture/excel/img_1.png


+ 0 - 0
doc/docs/picture/execl/img_2.png → doc/docs/picture/excel/img_2.png


+ 0 - 0
doc/docs/picture/execl/img_3.png → doc/docs/picture/excel/img_3.png


+ 0 - 0
doc/docs/picture/execl/img_4.png → doc/docs/picture/excel/img_4.png


+ 0 - 0
doc/docs/picture/execl/img_5.png → doc/docs/picture/excel/img_5.png


+ 3 - 0
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportexcel/service/impl/ReportExcelServiceImpl.java

@@ -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);

+ 7 - 0
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/controller/ReportShareController.java

@@ -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();
+    }
+
 }

+ 4 - 0
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/controller/dto/ReportShareDto.java

@@ -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;
+
 }

+ 19 - 5
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/controller/param/ReportShareParam.java

@@ -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;
 }

+ 3 - 1
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/dao/entity/ReportShare.java

@@ -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;
 
 }

+ 6 - 0
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/service/ReportShareService.java

@@ -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);
 }

+ 46 - 5
report-core/src/main/java/com/anjiplus/template/gaea/business/modules/reportshare/service/impl/ReportShareServiceImpl.java

@@ -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()));

+ 17 - 0
report-core/src/main/java/com/anjiplus/template/gaea/business/util/DateUtil.java

@@ -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);

+ 9 - 0
report-core/src/main/resources/db/migration/V1.0.21__update_widget_column.sql

@@ -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`;

+ 10 - 0
report-core/src/main/resources/db/migration/V1.0.22__add_report_share.sql

@@ -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;

+ 1 - 1
report-ui/config/dev.env.js

@@ -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 - 8
report-ui/src/api/reportShare.js

@@ -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}

二进制
report-ui/src/assets/images/apache.png


+ 2 - 131
report-ui/src/assets/styles/screenDesigner.scss

@@ -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;
-}
+

+ 210 - 193
report-ui/src/router/index.js

@@ -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
   },
 ]
 

+ 1 - 1
report-ui/src/utils/china.js

@@ -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],

+ 9 - 2
report-ui/src/views/bigScreenReport/components/share.vue

@@ -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: "创建链接成功!",

+ 5 - 1
report-ui/src/views/bigScreenReport/index.vue

@@ -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

+ 10 - 11
report-ui/src/views/bigscreenDesigner/designer/tools/configure/widget-more-bar-line.js

@@ -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,},
         ]
       },
       {

+ 6 - 12
report-ui/src/views/bigscreenDesigner/designer/widget/bar/widgetMoreBarLineChart.vue

@@ -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);

+ 15 - 3
report-ui/src/views/excelreport/components/share.vue

@@ -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: "创建链接成功!",

+ 85 - 0
report-ui/src/views/excelreport/el/index.vue

@@ -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>

+ 4 - 5
report-ui/src/views/excelreport/index.vue

@@ -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) {

+ 72 - 18
report-ui/src/views/layout/components/Navbar.vue

@@ -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;

+ 1 - 1
report-ui/src/views/layout/components/Sidebar/index.vue

@@ -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

+ 85 - 7
report-ui/src/views/login.vue

@@ -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 {

+ 9 - 2
report-ui/src/views/reportManage/components/share.vue

@@ -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: "创建链接成功!",

+ 3 - 5
report-ui/src/views/reportManage/index.vue

@@ -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;
     },
     //复制

+ 259 - 0
report-ui/src/views/reportShare/index.vue

@@ -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>

+ 32 - 7
report-ui/src/views/screenDesigner/components/contentMenu.vue

@@ -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]);
+      }
     }
   }
 };

+ 2 - 0
report-ui/src/views/screenDesigner/config/configs.js

@@ -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,

+ 8 - 0
report-ui/src/views/screenDesigner/config/index.js

@@ -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"
 

+ 77 - 536
report-ui/src/views/screenDesigner/index.vue

@@ -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);
 
-      //2022年02月22日 修复:可以拖拽放到鼠标的位置
-      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) {
-          //msSaveOrOpenBlob方法返回bool值
-          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]);
-      }
     }
   }
 };

+ 725 - 0
report-ui/src/views/screenDesigner/index备份.vue

@@ -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: [
+        {
+          // type和value最终存到数据库中去,保存到gaea_report_dashboard_widget中
+          type: "widget-text",
+          value: {
+            setup: {},
+            data: {},
+            position: {
+              width: 100,
+              height: 100,
+              left: 0,
+              top: 0,
+              zIndex: 0
+            }
+          },
+          // options属性是从工具栏中拿到的tools中拿到
+          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);
+
+      //2022年02月22日 修复:可以拖拽放到鼠标的位置
+      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) {
+          //msSaveOrOpenBlob方法返回bool值
+          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>

+ 144 - 0
report-ui/src/views/screenDesigner/layout/leftMenu.vue

@@ -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>

+ 260 - 0
report-ui/src/views/screenDesigner/layout/middleScreen.vue

@@ -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>

+ 70 - 0
report-ui/src/views/screenDesigner/layout/rightConfig.vue

@@ -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>

+ 229 - 0
report-ui/src/views/screenDesigner/layout/topBar.vue

@@ -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) {
+          //msSaveOrOpenBlob方法返回bool值
+          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>

+ 4 - 0
report-ui/src/views/screenDesigner/node.md

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

+ 92 - 0
report-ui/src/views/screenDesigner/util/revoke.js

@@ -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);
+    }
+}

+ 1 - 0
report-ui/src/views/screenDesigner/util/screen.js

@@ -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) {