From 7a56f172d2a004247147e85cca17a35d0233c825 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E4=B8=96=E6=9E=97?=
 <463572181@qq.com>
Date: Mon, 27 Feb 2023 14:00:36 +0800
Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E7=BB=84=E4=BB=B6=E8=81=94=E5=8A=A8?=
 =?UTF-8?q?=E6=A1=88=E4=BE=8B(=E6=9F=B1=E5=9B=BE=EF=BC=9A=E8=A7=A6?=
 =?UTF-8?q?=E5=8F=91=E8=80=85+=E6=8E=A5=E6=94=B6=E8=80=85;=20=E6=8A=98?=
 =?UTF-8?q?=E7=BA=BF=E5=9B=BE=EF=BC=9A=E8=A7=A6=E5=8F=91=E8=80=85+?=
 =?UTF-8?q?=E6=8E=A5=E6=94=B6=E8=80=85;=20=E7=99=BE=E5=88=86=E6=AF=94?=
 =?UTF-8?q?=E5=9B=BE=EF=BC=9A=E6=8E=A5=E6=94=B6=E8=80=85;=20)=202=E3=80=81?=
 =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=80=81=E9=A2=84=E8=A7=88=E3=80=81=E6=92=A4?=
 =?UTF-8?q?=E5=9B=9E=E6=8C=89=E9=92=AE=E5=8F=AF=E7=82=B9=E5=87=BB=E8=8C=83?=
 =?UTF-8?q?=E5=9B=B4=E8=B0=83=E6=95=B4;=203=E3=80=81=E6=8B=96=E5=85=A5?=
 =?UTF-8?q?=E7=BB=84=E4=BB=B6=E9=85=8D=E7=BD=AE=E9=A1=B9=E5=AE=9A=E4=BD=8D?=
 =?UTF-8?q?=E4=B8=8D=E5=87=86=E7=9A=84bug=E4=BF=AE=E5=A4=8D;=204=E3=80=81?=
 =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=9D=E5=A7=8B=E5=A4=A7=E5=B1=8F=E9=BB=98?=
 =?UTF-8?q?=E8=AE=A4=E8=83=8C=E6=99=AF=E8=89=B2=E4=B8=8D=E7=94=9F=E6=95=88?=
 =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98;?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 report-ui/src/store/index.js                  |   4 +-
 report-ui/src/store/modules/designer.js       |  42 +++
 .../designer/components/componentLinkage.vue  | 290 ++++++++++++++++++
 .../designer/components/dynamicForm.vue       |  23 ++
 .../bigscreenDesigner/designer/index.vue      |  79 ++++-
 .../designer/linkageLogic.js                  | 128 ++++++++
 .../configure/barCharts/widget-barchart.js    |  12 +
 .../configure/lineCharts/widget-linechart.js  |  12 +
 .../percentCharts/widget-pie-percentage.js    |  12 +
 .../bigscreenDesigner/designer/tools/index.js |   7 +-
 .../bigscreenDesigner/designer/tools/main.js  |   6 +-
 .../designer/widget/bar/widgetBarchart.vue    |  27 +-
 .../designer/widget/line/widgetLinechart.vue  |  27 +-
 .../percent/widgetPiePercentageChart.vue      |  26 +-
 .../designer/widget/temp.vue                  |   6 +-
 .../designer/widget/widget.vue                |   2 +-
 .../views/bigscreenDesigner/viewer/index.vue  |  11 +
 17 files changed, 684 insertions(+), 30 deletions(-)
 create mode 100644 report-ui/src/store/modules/designer.js
 create mode 100644 report-ui/src/views/bigscreenDesigner/designer/components/componentLinkage.vue
 create mode 100644 report-ui/src/views/bigscreenDesigner/designer/linkageLogic.js

diff --git a/report-ui/src/store/index.js b/report-ui/src/store/index.js
index f011f382..89cec40a 100644
--- a/report-ui/src/store/index.js
+++ b/report-ui/src/store/index.js
@@ -7,6 +7,7 @@ import app from './modules/app'
 import user from './modules/user'
 import cacheView from './modules/cachaView'
 import help from './modules/help'
+import designer from './modules/designer'
 
 Vue.use(Vuex)
 
@@ -18,7 +19,8 @@ const store = new Vuex.Store({
     app,
     user,
     cacheView,
-    help
+    help,
+    designer
   },
   state: { },
   plugins: [initPlugin],
diff --git a/report-ui/src/store/modules/designer.js b/report-ui/src/store/modules/designer.js
new file mode 100644
index 00000000..ae275cac
--- /dev/null
+++ b/report-ui/src/store/modules/designer.js
@@ -0,0 +1,42 @@
+/*
+ * @Author: chengsl
+ * @Date: 2022-11-08 10:30:37
+ * @LastEditors: chengsl
+ * @LastEditTime: 2023-02-24 09:54:34
+ * @Description: 设计器公用变量
+ */
+
+const designer = {
+  state: {
+    allComponentLinkage: [], // 所有组件之间的联动配置
+  },
+
+  mutations: {
+    SET_ALL_COMPONENT_LINKAGE: (state, params) => {
+      var { index = -1, widgetId = '', linkageArr } = params
+      try {
+        console.log('params---', params)
+        linkageArr = linkageArr.map(item => {
+          const arr = item.widgetValue.split('-$-')
+          return {
+            originId: widgetId,
+            targetId: arr[0],
+            targetName: arr[1],
+            paramsConfig: item.paramsConfig
+          }
+        })
+      } catch (error) {
+        linkageArr = [] // 兼容异常错误导致页面加载不出来
+      }
+      state.allComponentLinkage[index] = {
+        index: +index,
+        widgetId,
+        linkageArr
+      }
+    }
+  },
+
+  actions: {}
+}
+
+export default designer
diff --git a/report-ui/src/views/bigscreenDesigner/designer/components/componentLinkage.vue b/report-ui/src/views/bigscreenDesigner/designer/components/componentLinkage.vue
new file mode 100644
index 00000000..44f25771
--- /dev/null
+++ b/report-ui/src/views/bigscreenDesigner/designer/components/componentLinkage.vue
@@ -0,0 +1,290 @@
+<template>
+  <div class="component-linkage">
+    <el-button
+      type="primary"
+      size="mini"
+      icon="el-icon-plus"
+      :disabled="formData.length === layerWidget.length -1"
+      plain
+      @click="handleAddClick"
+    >
+      新增
+    </el-button>
+    <el-table :data="formData" style="width: 100%">
+      <el-table-column label="被联动组件名" align="left">
+        <template slot-scope="scope">
+          <div class="button-name" v-text="scope.row.widgetValue.split('-$-')[1]" />
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center">
+        <template slot-scope="scope">
+          <span
+            class="editor"
+            @click="handleEditorClick(scope.$index, scope.row)"
+          >
+            <i class="el-icon-edit" /> 编辑
+          </span>
+          <span
+            class="delete"
+            @click="handleDeleteClick(scope.$index, scope.row)"
+          >
+            <i class="el-icon-delete" /> 删除
+          </span>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <el-dialog
+      :title="isAddFlag ? '新增' : '修改'"
+      :visible.sync="dialogVisible"
+      width="30%"
+      :before-close="handleClose"
+    >
+      <el-form ref="myForm" v-model="linkageForm" label-width="100px">
+        <el-form-item label="被联动的组件">
+          <el-select
+            v-model="linkageForm.widgetValue"
+            size="mini"
+            clearable
+            placeholder="请选择"
+          >
+            <el-option
+              v-for="(item, index) in layerWidget"
+              :key="item.widgetId"
+              :disabled="widgetIndex === index || widgetIdList.includes(index)"
+              :label="item.label"
+              :value="`${item.widgetId}-$-${item.label}-$-${index}`"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item v-show="linkageForm.widgetValue" class="params-form-item" label="参数配置">
+          <div class="params-config">
+            <div
+              v-for="item in linkageForm.paramsConfig"
+              :key="item.originKey"
+              class="item-config"
+            >
+              <div class="label">{{ item.originKey }}</div>
+              <div class="value">
+                <el-select
+                  v-model="item.targetKey"
+                  size="mini"
+                  clearable
+                  placeholder="请选择"
+                >
+                  <el-option
+                    v-for="paramKey in currentTargetParams"
+                    :key="paramKey"
+                    :label="paramKey"
+                    :value="paramKey"
+                  />
+                </el-select>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button size="mini" @click="handleClose">取 消</el-button>
+        <el-button size="mini" type="primary" @click="handleSaveClick">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getOneConfigByCode } from '../linkageLogic'
+export default {
+  name: 'ComponentLinkage',
+  components: {},
+  model: {
+    prop: 'formData',
+    event: 'input'
+  },
+  props: {
+    formData: {
+      type: Array,
+      default: () => []
+    },
+    layerWidget: { // 当前设计器中所有组件的名称
+      type: Array,
+      default: () => []
+    },
+    widgetParamsConfig: { // 当前设计器中所有组件的的数据集配置
+      type: Array,
+      default: () => []
+    },
+    widgetIndex: { // 当前操作的组件下标
+      require: true,
+      type: Number,
+      default: -1
+    }
+  },
+  data() {
+    return {
+      isAddFlag: true, // true 新增, false 编辑
+      indexEditor: -1, // 编辑第几个数据
+      linkageForm: {
+        widgetValue: '', // 选中的组件的名字
+        paramsConfig: []
+      },
+      dialogVisible: false // 显示弹窗
+    }
+  },
+  computed: {
+    targetIndex() { // 当前选择联动的目标组件的下标
+      if (!this.linkageForm.widgetValue) return -1
+      return +this.linkageForm.widgetValue.split('-$-')[2]
+    },
+    currentTargetParams() { // 当前选择联动的目标组件的数据集参数
+      try {
+        return Object.keys(this.widgetParamsConfig[this.targetIndex].dynamicData.contextData)
+      } catch (error) {
+        return []
+      }
+    },
+    widgetIdList() {
+      return this.formData.map(item => {
+        return +item.widgetValue.split('-$-')[0]
+      })
+    }
+  },
+  watch: {
+    widgetIndex: {
+      handler(val) {
+        if (val !== -1) {
+          this.initFormDynamicData()
+        }
+      },
+      immediate: true
+    }
+  },
+  created() {
+  },
+  mounted() {},
+  methods: {
+    // 重置对象
+    initFormDynamicData() {
+      let paramsKey = []
+      const dynamicParamsWidget = ['widgetButtonGroup', 'widget-table']
+      if (dynamicParamsWidget.includes(this.layerWidget[this.widgetIndex].code)) { // 参数不确定的 通过消息接收
+        paramsKey = this.layerWidget[this.widgetIndex].paramsKeys || []
+      } else {
+        const widgetConfigTemp = getOneConfigByCode(this.layerWidget[this.widgetIndex].code)
+        if (!widgetConfigTemp) return
+        // console.log('this.layerWidget[this.widgetIndex---', this.layerWidget, '  ---  ', this.widgetIndex)
+        paramsKey = widgetConfigTemp.paramsKey
+      }
+      this.linkageForm = {
+        widgetValue: '', // 选中的组件的名字
+        paramsConfig: paramsKey.map(item => {
+          return {
+            originKey: item,
+            targetKey: ''
+          }
+        })
+      }
+    },
+    // 弹出框关闭
+    handleClose() {
+      this.dialogVisible = false
+      this.buttonLabel = ''
+      this.initFormDynamicData()
+    },
+    // 新增按钮
+    handleAddClick() {
+      this.buttonLabel = ''
+      this.initFormDynamicData()
+      this.isAddFlag = true
+      this.dialogVisible = true
+    },
+    // 修改按钮
+    handleEditorClick(index, row) {
+      this.isAddFlag = false
+      this.linkageForm = JSON.parse(JSON.stringify(this.formData[index]))
+      this.dialogVisible = true
+      this.indexEditor = index
+    },
+    // 删除
+    handleDeleteClick(index) {
+      this.formData.splice(index, 1)
+      this.$emit('input', this.formData)
+      this.$emit('change', this.formData)
+    },
+    // 确定
+    handleSaveClick() {
+      const obj = JSON.parse(JSON.stringify(this.linkageForm))
+      if (this.isAddFlag) {
+        // 新增
+        this.formData.push(obj)
+        this.dialogVisible = false
+      } else {
+        // 编辑
+        this.formData[this.indexEditor] = obj
+        this.dialogVisible = false
+      }
+      this.$emit('input', this.formData)
+      this.$emit('change', this.formData)
+    }
+  }
+}
+</script>
+<style lang='scss' scoped>
+.component-linkage {
+
+  .button-name {
+    width: 80px;
+    height: 30px;
+    line-height: 30px;
+    color: #A9B2BC;
+    border: 1px solid #23466F;
+    border-radius: 4px;
+    box-shadow: 0 0 3px #23466f inset;
+    text-align: center;
+  }
+  .editor, .delete {
+    color: #409eff;
+    cursor: pointer;
+  }
+  .delete {
+    margin-left: 10px;
+  }
+
+  /deep/.el-table,
+  /deep/.el-table__expanded-cell,
+  /deep/.el-table th,
+  /deep/.el-table tr {
+    background-color: transparent !important;
+    color: #859094 !important;
+  }
+  /deep/.el-table td,
+  /deep/.el-table th.is-leaf {
+    border-bottom: none;
+    line-height: 26px;
+  }
+  /deep/.el-table tbody tr:hover > td {
+    background-color: #263445 !important;
+  }
+  /deep/.el-table::before {
+    height: 0;
+  }
+  /deep/.el-dialog {
+    background: #1b1e25;
+    .el-dialog__title {
+      color: #fff;
+    }
+  }
+  .params-form-item {
+    margin-top: 20px;
+  }
+  .item-config {
+    display: flex;
+    flex-wrap: nowrap;
+    align-items: center;
+    margin-bottom: 20px;
+    .label {
+      margin-right: 20px;
+    }
+  }
+}
+</style>
diff --git a/report-ui/src/views/bigscreenDesigner/designer/components/dynamicForm.vue b/report-ui/src/views/bigscreenDesigner/designer/components/dynamicForm.vue
index 2e607796..c44db5c7 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/components/dynamicForm.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/components/dynamicForm.vue
@@ -294,6 +294,15 @@
                   v-model="formData[itemChildList.name]"
                   @change="changed($event, itemChildList.name)"
                 />
+                <componentLinkage
+                  v-if="itemChildList.type == 'componentLinkage'"
+                  :key="'cl-' + idx"
+                  v-model="formData[itemChildList.name]"
+                  :layer-widget="layerWidget"
+                  :widget-params-config="widgetParamsConfig"
+                  :widget-index="widgetIndex"
+                  @change="changed($event, itemChildList.name)"
+                />
               </template>
             </el-collapse-item>
           </el-collapse>
@@ -319,6 +328,7 @@ import dynamicAddTable from "./dynamicAddTable.vue";
 import customUpload from "./customUpload.vue";
 import dynamicAddRadar from "./dynamicAddRadar";
 import MonacoEditor from "@/components/MonacoEditor/index";
+import componentLinkage from './componentLinkage';
 export default {
   name: "DynamicForm",
   components: {
@@ -330,6 +340,7 @@ export default {
     customUpload,
     dynamicAddRadar,
     MonacoEditor,
+    componentLinkage
   },
   model: {
     prop: "value",
@@ -341,6 +352,18 @@ export default {
       type: [Object],
       default: () => {},
     },
+    layerWidget: {
+      type: Array,
+      default: () => []
+    },
+    widgetParamsConfig: {
+      type: Array,
+      default: () => []
+    },
+    widgetIndex: {
+      type: Number,
+      default: -1
+    }
   },
   data() {
     return {
diff --git a/report-ui/src/views/bigscreenDesigner/designer/index.vue b/report-ui/src/views/bigscreenDesigner/designer/index.vue
index 63f0aa98..200a1a67 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/index.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/index.vue
@@ -80,46 +80,46 @@
       :style="{ width: middleWidth + 'px', height: middleHeight + 'px' }"
     >
       <div class="top-button">
-        <span class="btn">
+        <span class="btn"  @click="saveData">
           <el-tooltip
             class="item"
             effect="dark"
             content="保存"
             placement="bottom"
           >
-            <i class="iconfont iconsave" @click="saveData"></i>
+            <i class="iconfont iconsave"></i>
           </el-tooltip>
         </span>
-        <span class="btn">
+        <span class="btn"  @click="viewScreen">
           <el-tooltip
             class="item"
             effect="dark"
             content="预览"
             placement="bottom"
           >
-            <i class="iconfont iconyulan" @click="viewScreen"></i>
+            <i class="iconfont iconyulan"></i>
           </el-tooltip>
         </span>
 
-        <span class="btn">
+        <span class="btn"  @click="handleUndo">
           <el-tooltip
             class="item"
             effect="dark"
             content="撤销"
             placement="bottom"
           >
-            <i class="iconfont iconundo" @click="handleUndo"></i>
+            <i class="iconfont iconundo"></i>
           </el-tooltip>
         </span>
 
-        <span class="btn">
+        <span class="btn"  @click="handleRedo">
           <el-tooltip
             class="item"
             effect="dark"
             content="恢复"
             placement="bottom"
           >
-            <i class="iconfont iconhuifubeifen" @click="handleRedo"></i>
+            <i class="iconfont iconhuifubeifen"></i>
           </el-tooltip>
         </span>
 
@@ -321,6 +321,9 @@
           <dynamicForm
             ref="formData"
             :options="widgetOptions.setup"
+            :layer-widget="layerWidget"
+            :widget-index="widgetIndex"
+            :widget-params-config="widgetParamsConfig"
             @onChanged="(val) => widgetValueChanged('setup', val)"
           />
         </el-tab-pane>
@@ -412,7 +415,7 @@ export default {
         title: "", // 大屏页面标题
         width: 1920, // 大屏设计宽度
         height: 1080, // 大屏设计高度
-        backgroundColor: "", // 大屏背景色
+        backgroundColor: "#1E1E1E", // 大屏背景色
         backgroundImage: "", // 大屏背景图片
         refreshSeconds: null, // 大屏刷新时间间隔
         presetLine: [], // 辅助线
@@ -460,7 +463,9 @@ export default {
       activeName: "first",
       scaleNum: 0, // 当前缩放百分比的值
       sizeRange: [20, 40, 60, 80, 100, 150, 200, 300, 400], // 缩放百分比
-      currentSizeRangeIndex: -1 // 当前是哪个缩放比分比
+      currentSizeRangeIndex: -1, // 当前是哪个缩放比分比,
+      currentWidgetTotal: 0,
+       widgetParamsConfig: [], // 各组件动态数据集的参数配置情况
     };
   },
   computed: {
@@ -530,6 +535,7 @@ export default {
     widgets: {
       handler(val) {
         this.handlerLayerWidget(val);
+        this.handlerdynamicDataParamsConfig(val)
         //以下部分是记录历史
         this.$nextTick(() => {
           this.revoke.push(this.widgets);
@@ -638,7 +644,13 @@ export default {
       const layerWidgetArr = [];
       for (let i = 0; i < val.length; i++) {
         const obj = {};
-        obj.icon = getToolByCode(val[i].type).icon;
+        const myItem = getToolByCode(val[i].type)
+        obj.icon = myItem.icon;
+        obj.code = myItem.code // 组件类型code
+        obj.widgetId = val[i].value.widgetId || '' // 唯一id
+        if (val[i].value.paramsKeys) {
+          obj.paramsKeys = val[i].value.paramsKeys
+        }
         const options = val[i].options["setup"];
         options.forEach((el) => {
           if (el.name == "layerName") {
@@ -649,6 +661,12 @@ export default {
       }
       this.layerWidget = layerWidgetArr;
     },
+    // 返回每个组件的动态数据集参数配置情况
+    handlerdynamicDataParamsConfig(val) {
+      this.widgetParamsConfig = val.map(item => {
+        return item.value.data
+      })
+    },
     async initEchartData() {
       const reportCode = this.$route.query.reportCode;
       const { code, data } = await detailDashboard(reportCode);
@@ -672,7 +690,7 @@ export default {
       }
       this.setOptionsOnClickScreen();
       return {
-        backgroundColor: (data && data.backgroundColor) || "",
+        backgroundColor: (data && data.backgroundColor) || (!data ? '#1e1e1e' : ''),
         backgroundImage: (data && data.backgroundImage) || "",
         height: (data && data.height) || "1080",
         title: (data && data.title) || "",
@@ -690,10 +708,19 @@ export default {
           data: widgets[i].value.data,
           position: widgets[i].value.position,
         };
-        const tool = this.deepClone(getToolByCode(widgets[i].type));
+        const tool = this.deepClone(getToolByCode(widgets[i].type))
+        if (!tool) {
+          const message = '暂未提供该组件或该组件下线了,组件code: ' + widgets[i].type
+          console.error(message)
+          if (process.env.NODE_ENV === 'development') { // 40@remarks 看生产要不要提示
+            this.$message.error(message)
+          }
+          continue // 找不到就跳过,避免整个报表都加载不出来
+        }
         const option = tool.options;
         const options = this.handleOptionsData(widgets[i].value, option);
         obj.options = options;
+        obj.value.widgetId = obj.value.setup.widgetId
         widgetsData.push(obj);
       }
       return widgetsData;
@@ -753,6 +780,9 @@ export default {
         },
         widgets: this.widgets,
       };
+      screenData.widgets.forEach(widget => {
+        widget.value.setup.widgetId = widget.value.widgetId
+      })
       const { code, data } = await insertDashboard(screenData);
       if (code == "200") {
         this.$message.success("保存成功!");
@@ -831,9 +861,24 @@ export default {
     },
     dragStart(widgetCode) {
       this.dragWidgetCode = widgetCode;
+      this.currentWidgetTotal = this.widgets.length // 当前操作面板上有多少各组件
     },
     dragEnd() {
-      this.dragWidgetCode = "";
+      this.dragWidgetCode = "";/**
+       * 40@remarks 新增组件到操作面板后,右边的配置有更新,但是当前选中的组件没更新,导致配置错乱的bug;
+       * 由于拖动组件拖到非操作面板上是不会添加组件,还需判断是否添加组件到操作面板上;
+       */
+      this.$nextTick(()=>{
+        if (this.widgets.length === this.currentWidgetTotal + 1) { // 确实新增了一个组件到操作面板上
+          console.log(`新添加 '${this.widgets[this.currentWidgetTotal].value.setup.layerName}' 组件到操作面板`)
+          const uuid = Number(Math.random().toString().substr(2)).toString(36)
+          this.widgets[this.currentWidgetTotal].value.widgetId = uuid
+          this.layerWidget[this.currentWidgetTotal].widgetId = uuid
+          const index = this.widgets.length - 1
+          this.layerClick(index) // 选中当前新增的组件
+          this.grade = false // 去除网格线
+        }
+      })
     },
     dragOver(evt) {
       evt.preventDefault();
@@ -1080,7 +1125,13 @@ export default {
     // 复制
     copylayer() {
       const obj = this.deepClone(this.widgets[this.rightClickIndex]);
+      obj.value.position.top += 40 // 复制的元素向右下角偏移一点
+      obj.value.position.left += 40
+      obj.value.widgetId = Number(Math.random().toString().substr(2)).toString(36)
       this.widgets.splice(this.widgets.length, 0, obj);
+      this.$nextTick(() => {
+        this.layerClick(this.widgets.length - 1) // 复制后定位到最新的组件
+      })
     },
     // 置顶
     istopLayer() {
diff --git a/report-ui/src/views/bigscreenDesigner/designer/linkageLogic.js b/report-ui/src/views/bigscreenDesigner/designer/linkageLogic.js
new file mode 100644
index 00000000..b00bf3f6
--- /dev/null
+++ b/report-ui/src/views/bigscreenDesigner/designer/linkageLogic.js
@@ -0,0 +1,128 @@
+/*
+ * @Author: chengsl
+ * @Date: 2023-02-24 09:40:13
+ * @LastEditors: chengsl
+ * @LastEditTime: 2023-02-24 13:12:24
+ * @Description: 各联动组件的参数配置 参数paramsKey的值具体封装时再改
+ */
+import { eventBus as bus } from "@/utils/eventBus";
+export const lickageParamsConfig = [
+  // {
+  //   name: '按钮组',
+  //   code: 'widgetButtonGroup',
+  //   paramsKey: [] // 40@remarks 动态:[...row, index]
+  // },
+  {
+    name: '柱图',
+    code: 'widget-barchart',
+    paramsKey: ['name', 'value']
+  },
+  // ……
+  {
+    name: '折线图',
+    code: 'widget-linechart',
+    paramsKey: ['name', 'value']
+  },
+  {
+    name: '百分比图',
+    code: 'widgetPiePercentageChart',
+    paramsKey: ['value']
+  },
+]
+
+export const getOneConfigByCode = function(code) {
+  return lickageParamsConfig.find(item => { return item.code === code })
+}
+
+export const getOneConfigByName = function(name) {
+  return lickageParamsConfig.find(item => { return item.name === name })
+}
+
+/**
+ * 源组件 - 初始化联动逻辑
+ * @param self 组件实例对象 this
+ * @param isActiveClick 主动触发(非echart类click事件触发)
+ * @param buttonConfig 按钮组组件的配置
+ * 40@remarks
+ * 1、v-chart 需添加 ref="myVChart" 以获取实例
+ * 2、 发消息发过去的对象 待封装配置动态兼容
+ */
+export const originWidgetLinkageLogic = function(self, isActiveClick = false, buttonConfig = {}) {
+  // if (self.allComponentLinkage && self.allComponentLinkage.length && self.allComponentLinkage[self.widgetIndex].index !== -1 && self.allComponentLinkage[self.widgetIndex].linkageArr.length) {
+  if (self.optionsSetup.componentLinkage && self.optionsSetup.componentLinkage.length) {
+    if (isActiveClick) { // 主动触发
+      self.allComponentLinkage[self.widgetIndex].linkageArr.forEach(item => {
+        console.log(`bus_${item.originId}_${item.targetId}`, ' -联动逻辑点击-发送消息', buttonConfig)
+        bus.$emit(`bus_${item.originId}_${item.targetId}`, buttonConfig.currentData)
+      })
+    } else { // chart 组件
+      self.$refs.myVChart.chart.on('click', function(params) {
+        self.allComponentLinkage[self.widgetIndex].linkageArr.forEach(item => {
+          console.log(`bus_${item.originId}_${item.targetId}`, ' -联动逻辑点击-发送消息', params)
+          let message = {}
+          const widgetConfigTemp = getOneConfigByCode(self.value.widgetCode)
+          if (widgetConfigTemp && widgetConfigTemp.paramsKey.length) { // 动态加载各组件的参数来封装
+            widgetConfigTemp.paramsKey.forEach(key => {
+              message[key] = params[key]
+            })
+            // 40@remarks 部分组件 传参需要特殊处理下
+            // ……
+            // 40@remarks 专用于测试联动发消息 手动改造消息内容
+            // if (self.value.widgetCode === 'widgetMap2d') {
+            //   const nameTemp = ['苹果', '三星', '小米', '华为', 'OPPO', 'VIVO']
+            //   // message = {
+            //   //   name: nameTemp[(params.dataIndex % 6)],
+            //   //   value: params.value,
+            //   //   dataIndex: params.dataIndex
+            //   // }
+            //   // message.name = nameTemp[(+params.value % 6)]
+            //   message.name = nameTemp[(parseInt(Math.random() * 6) % 6)]
+            // }
+            // if (self.value.widgetCode === 'widget-piechart') {
+            //   message.name = (parseInt(Math.random() * 2) % 2) === 0 ? '深圳市' : '盐田区'
+            // }
+          } else {
+            message = {
+              name: params.name,
+              value: params.value
+            }
+          }
+          bus.$emit(`bus_${item.originId}_${item.targetId}`, message)
+        })
+      })
+    }
+  }
+}
+
+/**
+ * 目标组件 - 初始化联动逻辑
+ * @param self 组件实例对象 this
+ * @returns
+ */
+export const targetWidgetLinkageLogic = function(self) {
+  const busEvents = []
+  // 有无有关联的组件
+  if (!self.allComponentLinkage || !self.allComponentLinkage.length) return
+  self.allComponentLinkage.some(item => {
+    if (item.index !== -1 && item.linkageArr.length) {
+      item.linkageArr.some(obj => {
+        if (obj.targetId === self.value.setup.widgetId) {
+          self.hasLinkage = true
+          busEvents.push({
+            eventName: `bus_${obj.originId}_${obj.targetId}`,
+            paramsConfig: obj.paramsConfig
+          })
+          return true
+        }
+      })
+    }
+  })
+  if (self.hasLinkage) {
+    busEvents.forEach(item => {
+      bus.$on(item.eventName, e => {
+        console.log(item.eventName, ' 接收消息e', e)
+        self.setOptionsData(e, item.paramsConfig)
+      })
+    })
+  }
+}
diff --git a/report-ui/src/views/bigscreenDesigner/designer/tools/configure/barCharts/widget-barchart.js b/report-ui/src/views/bigscreenDesigner/designer/tools/configure/barCharts/widget-barchart.js
index 2f55a78c..c55403b4 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/tools/configure/barCharts/widget-barchart.js
+++ b/report-ui/src/views/bigscreenDesigner/designer/tools/configure/barCharts/widget-barchart.js
@@ -564,6 +564,18 @@ export const widgetBarchart = {
             },
           ],
         },
+        {
+          name: '组件联动',
+          list: [
+            {
+              type: 'componentLinkage',
+              label: '',
+              name: 'componentLinkage',
+              required: false,
+              value: []
+            }
+          ]
+        }
       ],
     ],
     // 数据
diff --git a/report-ui/src/views/bigscreenDesigner/designer/tools/configure/lineCharts/widget-linechart.js b/report-ui/src/views/bigscreenDesigner/designer/tools/configure/lineCharts/widget-linechart.js
index f9fc91a1..b47cbdc6 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/tools/configure/lineCharts/widget-linechart.js
+++ b/report-ui/src/views/bigscreenDesigner/designer/tools/configure/lineCharts/widget-linechart.js
@@ -587,6 +587,18 @@ export const widgetLinechart = {
             },
           ],
         },
+        {
+          name: '组件联动',
+          list: [
+            {
+              type: 'componentLinkage',
+              label: '',
+              name: 'componentLinkage',
+              required: false,
+              value: []
+            }
+          ]
+        }
       ],
     ],
     // 数据
diff --git a/report-ui/src/views/bigscreenDesigner/designer/tools/configure/percentCharts/widget-pie-percentage.js b/report-ui/src/views/bigscreenDesigner/designer/tools/configure/percentCharts/widget-pie-percentage.js
index a8a349fe..3d1d9fc1 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/tools/configure/percentCharts/widget-pie-percentage.js
+++ b/report-ui/src/views/bigscreenDesigner/designer/tools/configure/percentCharts/widget-pie-percentage.js
@@ -169,6 +169,18 @@ export const widgetPiePercentage = {
               value: '#173164'
             },
           ]
+        },
+        {
+          name: '组件联动',
+          list: [
+            {
+              type: 'componentLinkage',
+              label: '',
+              name: 'componentLinkage',
+              required: false,
+              value: []
+            }
+          ]
         }
       ],
     ],
diff --git a/report-ui/src/views/bigscreenDesigner/designer/tools/index.js b/report-ui/src/views/bigscreenDesigner/designer/tools/index.js
index 4a298073..7a20a42d 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/tools/index.js
+++ b/report-ui/src/views/bigscreenDesigner/designer/tools/index.js
@@ -3,8 +3,8 @@
  * @version:
  * @Author: qianlishi
  * @Date: 2021-08-29 06:43:07
- * @LastEditors: qianlishi qianlishi@anji-plus.com
- * @LastEditTime: 2022-11-07 15:35:42
+ * @LastEditors: chengsl
+ * @LastEditTime: 2023-02-24 10:29:26
  */
 import { widgetTool } from "./main"
 const screenConfig = {
@@ -52,7 +52,7 @@ const screenConfig = {
         name: 'backgroundColor',
         required: false,
         placeholder: '',
-        value: 'rgba(45, 86, 126, 1)',
+        value: '#1E1E1E',
       },
       {
         type: 'custom-upload',
@@ -72,6 +72,7 @@ export const converArr = (data) => {
   let tempArr = [], newArr = []
   for (let i = 0; i < data.length; i++) {
     const item = data[i]
+    item.widgetId = ''
     if (tempArr.indexOf(item.type) === -1) {
       newArr.push({
         name: item.tabName,
diff --git a/report-ui/src/views/bigscreenDesigner/designer/tools/main.js b/report-ui/src/views/bigscreenDesigner/designer/tools/main.js
index 660c04cc..e7159139 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/tools/main.js
+++ b/report-ui/src/views/bigscreenDesigner/designer/tools/main.js
@@ -3,8 +3,8 @@
  * @version:
  * @Author: qianlishi
  * @Date: 2021-08-29 07:46:46
- * @LastEditors: qianlishi qianlishi@anji-plus.com
- * @LastEditTime: 2023-01-09 13:16:19
+ * @LastEditors: chengsl
+ * @LastEditTime: 2023-02-23 15:23:20
  */
 
 import { widgetText } from "./configure/texts/widget-text"
@@ -70,7 +70,7 @@ export const widgetTool = [
   widgetLineCompare,
   widgetDecoratePie,
   widgetMoreBarLine,
-  widgetWordCloud,
+  // widgetWordCloud,
   widgetHeatmap,
   widgetRadar,
   widgetBarLineStack,
diff --git a/report-ui/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue b/report-ui/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue
index c4d12d21..bfcd0ce7 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/widget/bar/widgetBarchart.vue
@@ -1,10 +1,11 @@
 <template>
   <div :style="styleObj">
-    <v-chart :options="options" autoresize />
+    <v-chart ref="myVChart" :options="options" autoresize />
   </div>
 </template>
 
 <script>
+import { originWidgetLinkageLogic, targetWidgetLinkageLogic } from '@/views/bigscreenDesigner/designer/linkageLogic'
 import { eventBusParams } from "@/utils/screen";
 export default {
   name: "WidgetBarchart",
@@ -12,6 +13,10 @@ export default {
   props: {
     value: Object,
     ispreview: Boolean,
+    widgetIndex: {
+      type: Number,
+      default: 0
+    }, // 当前组件,在工作区变量widgetInWorkbench中的索引
   },
   data() {
     return {
@@ -70,6 +75,9 @@ export default {
         background: this.optionsSetup.background,
       };
     },
+    allComponentLinkage() {
+      return this.$store.state.designer.allComponentLinkage
+    }
   },
   watch: {
     value: {
@@ -89,6 +97,8 @@ export default {
     this.optionsCollapse = this.value.setup;
     this.optionsSetup = this.value.setup;
     this.editorOptions();
+    targetWidgetLinkageLogic(this) // 联动-目标组件逻辑
+    originWidgetLinkageLogic(this) // 联动-源组件逻辑
 
     eventBusParams(
       this.optionsSetup,
@@ -302,9 +312,22 @@ export default {
       this.options = Object.assign({}, this.options);
     },
     // 数据解析
-    setOptionsData() {
+    setOptionsData(e, paramsConfig) {
       const optionsSetup = this.optionsSetup;
       const optionsData = this.optionsData; // 数据类型 静态 or 动态
+      // 联动接收者逻辑开始
+      optionsData.dynamicData = optionsData.dynamicData || {} // 兼容 dynamicData undefined
+      const myDynamicData = optionsData.dynamicData
+      clearInterval(this.flagInter) // 不管咋,先干掉上一次的定时任务,避免多跑
+      if (e && optionsData.dataType !== 'staticData' && Object.keys(myDynamicData.contextData).length) {
+        const keyArr = Object.keys(myDynamicData.contextData)
+        paramsConfig.forEach(conf => {
+          if (keyArr.includes(conf.targetKey)) {
+            myDynamicData.contextData[conf.targetKey] = e[conf.originKey]
+          }
+        })
+      }
+      // 联动接收者逻辑结束
       optionsData.dataType == "staticData"
         ? this.staticDataFn(optionsData.staticData)
         : this.dynamicDataFn(optionsData.refreshTime);
diff --git a/report-ui/src/views/bigscreenDesigner/designer/widget/line/widgetLinechart.vue b/report-ui/src/views/bigscreenDesigner/designer/widget/line/widgetLinechart.vue
index fe88928a..c2f23057 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/widget/line/widgetLinechart.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/widget/line/widgetLinechart.vue
@@ -1,10 +1,11 @@
 <template>
   <div :style="styleObj">
-    <v-chart :options="options" autoresize />
+    <v-chart ref="myVChart" :options="options" autoresize />
   </div>
 </template>
 
 <script>
+import { originWidgetLinkageLogic, targetWidgetLinkageLogic } from '@/views/bigscreenDesigner/designer/linkageLogic'
 import { eventBusParams } from "@/utils/screen";
 export default {
   name: "WidgetLinechart",
@@ -12,6 +13,10 @@ export default {
   props: {
     value: Object,
     ispreview: Boolean,
+    widgetIndex: {
+      type: Number,
+      default: 0
+    }, // 当前组件,在工作区变量widgetInWorkbench中的索引
   },
   data() {
     return {
@@ -76,6 +81,9 @@ export default {
         background: this.optionsSetup.background,
       };
     },
+    allComponentLinkage() {
+      return this.$store.state.designer.allComponentLinkage
+    }
   },
   watch: {
     value: {
@@ -89,12 +97,14 @@ export default {
       deep: true,
     },
   },
-  created() {
+  mounted() {
     this.optionsStyle = this.value.position;
     this.optionsData = this.value.data;
     this.optionsCollapse = this.value.collapse;
     this.optionsSetup = this.value.setup;
     this.editorOptions();
+    targetWidgetLinkageLogic(this) // 联动-目标组件逻辑
+    originWidgetLinkageLogic(this) // 联动-源组件逻辑
     eventBusParams(
       this.optionsSetup,
       this.optionsData,
@@ -299,8 +309,19 @@ export default {
       this.options = Object.assign({}, this.options);
     },
     // 处理数据
-    setOptionsData() {
+    setOptionsData(e, paramsConfig) {
       const optionsData = this.optionsData; // 数据类型 静态 or 动态
+      optionsData.dynamicData = optionsData.dynamicData || {} // 兼容 dynamicData undefined
+      const myDynamicData = optionsData.dynamicData
+      clearInterval(this.flagInter) // 不管咋,先干掉上一次的定时任务,避免多跑
+      if (e && optionsData.dataType !== 'staticData' && Object.keys(myDynamicData.contextData).length) {
+        const keyArr = Object.keys(myDynamicData.contextData)
+        paramsConfig.forEach(conf => {
+          if (keyArr.includes(conf.targetKey)) {
+            myDynamicData.contextData[conf.targetKey] = e[conf.originKey]
+          }
+        })
+      }
       optionsData.dataType == "staticData"
         ? this.staticDataFn(optionsData.staticData)
         : this.dynamicDataFn(optionsData.dynamicData, optionsData.refreshTime);
diff --git a/report-ui/src/views/bigscreenDesigner/designer/widget/percent/widgetPiePercentageChart.vue b/report-ui/src/views/bigscreenDesigner/designer/widget/percent/widgetPiePercentageChart.vue
index 86463d26..c6b69314 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/widget/percent/widgetPiePercentageChart.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/widget/percent/widgetPiePercentageChart.vue
@@ -1,10 +1,11 @@
 <template>
   <div :style="styleObj">
-    <v-chart :options="options" autoresize />
+    <v-chart ref="myVChart" :options="options" autoresize />
   </div>
 </template>
 
 <script>
+import { targetWidgetLinkageLogic } from '@/views/bigscreenDesigner/designer/linkageLogic'
 import { eventBusParams } from "@/utils/screen";
 let per = 60;
 export default {
@@ -13,6 +14,10 @@ export default {
   props: {
     value: Object,
     ispreview: Boolean,
+    widgetIndex: {
+      type: Number,
+      default: 0
+    }, // 当前组件,在工作区变量widgetInWorkbench中的索引
   },
   data() {
     return {
@@ -325,6 +330,9 @@ export default {
         background: this.optionsSetup.background,
       };
     },
+    allComponentLinkage() {
+      return this.$store.state.designer.allComponentLinkage
+    }
   },
   watch: {
     value: {
@@ -358,6 +366,7 @@ export default {
               this.angle = this.angle + 3
               myChart.setOption(options,true)
             }, 1000);*/
+    targetWidgetLinkageLogic(this) // 联动-目标组件逻辑
   },
   methods: {
     //轴point设置
@@ -437,8 +446,21 @@ export default {
       line["lineStyle"] = lineStyle;
     },
     // 数据解析
-    setOptionsData() {
+    setOptionsData(e, paramsConfig) {
       const optionsData = this.optionsData; // 数据类型 静态 or 动态
+      optionsData.dynamicData = optionsData.dynamicData || {} // 兼容 dynamicData undefined
+
+      const myDynamicData = optionsData.dynamicData
+      clearInterval(this.flagInter) // 不管咋,先干掉上一次的定时任务,避免多跑
+      if (e && optionsData.dataType !== 'staticData' && Object.keys(myDynamicData.contextData).length) {
+        const keyArr = Object.keys(myDynamicData.contextData)
+        paramsConfig.forEach(conf => {
+          if (keyArr.includes(conf.targetKey)) {
+            myDynamicData.contextData[conf.targetKey] = e[conf.originKey]
+          }
+        })
+      }
+
       optionsData.dataType == "staticData"
         ? this.staticDataFn(optionsData.staticData)
         : this.dynamicDataFn(optionsData.dynamicData, optionsData.refreshTime);
diff --git a/report-ui/src/views/bigscreenDesigner/designer/widget/temp.vue b/report-ui/src/views/bigscreenDesigner/designer/widget/temp.vue
index a9e29d42..17a311eb 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/widget/temp.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/widget/temp.vue
@@ -6,7 +6,7 @@
  !-->
 <template>
   <div>
-    <component :is="type" :value="value" :ispreview="true" />
+    <component :is="type" :value="value" :ispreview="true" :widget-index="index" />
   </div>
 </template>
 
@@ -90,6 +90,10 @@ export default {
       type: [Object],
       default: () => {},
     },
+    index: {
+      type: Number,
+      default: 0
+    }, // 当前组件,在工作区变量widgetInWorkbench中的索引
   },
   data() {
     return {};
diff --git a/report-ui/src/views/bigscreenDesigner/designer/widget/widget.vue b/report-ui/src/views/bigscreenDesigner/designer/widget/widget.vue
index fedd824f..96f80fba 100644
--- a/report-ui/src/views/bigscreenDesigner/designer/widget/widget.vue
+++ b/report-ui/src/views/bigscreenDesigner/designer/widget/widget.vue
@@ -17,7 +17,7 @@
     @blur="handleBlur"
   >
     <!-- :z-index="-1" -->
-    <component :is="type" :value="value" />
+    <component :is="type":widget-index="index" :value="value" />
   </avue-draggable>
 </template>
 
diff --git a/report-ui/src/views/bigscreenDesigner/viewer/index.vue b/report-ui/src/views/bigscreenDesigner/viewer/index.vue
index 91b00976..e8ef91a1 100644
--- a/report-ui/src/views/bigscreenDesigner/viewer/index.vue
+++ b/report-ui/src/views/bigscreenDesigner/viewer/index.vue
@@ -12,6 +12,7 @@
         v-for="(widget, index) in widgets"
         :key="index"
         v-model="widget.value"
+        :index="index"
         :type="widget.type"
       />
     </div>
@@ -56,6 +57,16 @@ export default {
         transform: `scale(${ratioEquipment}, ${ratioEquipment})`,
         "transform-origin": "0 0"
       };
+      data.dashboard.widgets.forEach((item, index) => {
+        item.value.widgetId = item.value.setup.widgetId
+        if (item.value.setup.componentLinkage && item.value.setup.componentLinkage.length) {
+          this.$store.commit('SET_ALL_COMPONENT_LINKAGE', {
+            index,
+            widgetId: item.value.widgetId,
+            linkageArr: item.value.setup.componentLinkage
+          })
+        }
+      })
       this.widgets = data.dashboard.widgets;
     }
   }