Răsfoiți Sursa

优化 大屏

qianlishi 3 ani în urmă
părinte
comite
4cb06437d5

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

@@ -4,6 +4,6 @@ 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://127.0.0.1:9095"'
+  BASE_API: '"http://10.108.26.197:9095"'
 })

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

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

@@ -180,7 +180,8 @@ export default {
     },
     openDesign(val) {
       let routeUrl = this.$router.resolve({
-        path: "/bigscreen/designer",
+        path: "/screenDesigner",
+        // path: "/bigscreen/designer",
         query: {
           reportCode: val.reportCode
         }

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

+ 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-05-14 11:51:45
+-->
+<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">
+                <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) {