Răsfoiți Sursa

Merge branch 'eta1.6.3'

cxmo 9 luni în urmă
părinte
comite
c9d8648b58
30 a modificat fișierele cu 3634 adăugiri și 87 ștergeri
  1. 10 0
      src/api/modules/chartApi.js
  2. 153 11
      src/api/modules/chartRelevanceApi.js
  3. BIN
      src/assets/img/icons/formula-add.png
  4. 4 0
      src/lang/commonLang.js
  5. 73 2
      src/lang/modules/StatisticAnalysis/ChartRelevance.js
  6. 11 0
      src/routes/modules/chartRoutes.js
  7. 1 0
      src/utils/buttonConfig.js
  8. 54 14
      src/views/chartRelevance_manage/components/chartCard.vue
  9. 10 2
      src/views/chartRelevance_manage/components/explainText.js
  10. 111 0
      src/views/chartRelevance_manage/components/saveChartSetting.vue
  11. 44 8
      src/views/chartRelevance_manage/components/saveChartTobaseDia.vue
  12. 2 2
      src/views/chartRelevance_manage/components/saveEdbToBaseDia.vue
  13. 5 0
      src/views/chartRelevance_manage/components/selectTarget.vue
  14. 4 1
      src/views/chartRelevance_manage/css/index.scss
  15. 10 2
      src/views/chartRelevance_manage/mixins/classifyMixin.js
  16. 210 0
      src/views/chartRelevance_manage/relevance/components/batchSelectFormula.vue
  17. 257 0
      src/views/chartRelevance_manage/relevance/components/batchSelectTable.vue
  18. 38 0
      src/views/chartRelevance_manage/relevance/components/formMixin.js
  19. 137 0
      src/views/chartRelevance_manage/relevance/components/modifyClassifyDialog.vue
  20. 616 0
      src/views/chartRelevance_manage/relevance/components/multipleIndForm.vue
  21. 268 0
      src/views/chartRelevance_manage/relevance/components/singleIndForm.vue
  22. 294 23
      src/views/chartRelevance_manage/relevance/list.vue
  23. 1089 0
      src/views/chartRelevance_manage/relevance/relevanceChartEditorV2.vue
  24. 111 0
      src/views/chartRelevance_manage/relevance/utils/config.js
  25. 70 0
      src/views/chartRelevance_manage/relevance/utils/index.js
  26. 17 4
      src/views/dataEntry_manage/components/SaveChartOther.vue
  27. 2 2
      src/views/dataEntry_manage/mixins/chartPublic.js
  28. 16 4
      src/views/mychart_manage/components/chartDetailDia.vue
  29. 15 11
      src/views/ppt_manage/mixins/pptMixins.js
  30. 2 1
      src/views/ppt_manage/newVersion/utils/untils.js

+ 10 - 0
src/api/modules/chartApi.js

@@ -411,10 +411,20 @@ const dataBaseInterface = {
 		return http.post('/datamanage/edb_info/batch/add',params)
 	},
 	// 批量计算指标时获取当前筛选条件下选择的指标
+	/**
+	 * @param {Object} params
+	 * @param {Number} params.EdbInfoType 0-指标;1-预测指标
+	 * @returns 
+	 */
 	getBatchFilterAddEdbList:params=>{
 		return http.get('/datamanage/edb_info/calculate/multi/choice',params)
 	},
 	//批量计算指标时待选指标的搜索列表
+	/**
+	 * @param {Object} params
+	 * @param {Number} params.EdbInfoType 0-指标;1-预测指标
+	 * @returns 
+	 */
 	getBatchAddEdbSearchList:params=>{
 		return http.get('/datamanage/edb_info/calculate/multi/search',params)
 	},

+ 153 - 11
src/api/modules/chartRelevanceApi.js

@@ -3,11 +3,22 @@ import http from "@/api/http.js"
 export default{
     /**
    * 获取分类
+   * IsShowMe 是否仅展示我的:true-是
+   * ParentId 父级ID,一级分类不传或者0即可
+   * Source 来源:3-相关性(不传默认也为这个);4-滚动相关性
    * @returns 
    */
   classifyList:  params =>{
     return http.get('/correlation/chart_classify/list',params)
   },
+  /**
+   * 分类树
+   * @param {*} params 
+   * @returns 
+   */
+  classifyTree:params=>{
+    return http.get('/correlation/chart_classify/tree',params)
+  },
 
   /**
    * 新增分类
@@ -46,8 +57,14 @@ export default{
   },
 
   /**
-   * 移动分类
-   * @param {*} params ClassifyId "PrevClassifyId":1, "NextClassifyId":2,
+   * 移动分类or图表
+   * @param {*} params "ClassifyId": 228, //分类ID
+    "ParentClassifyId": 0, //父级分类ID
+    "PrevClassifyId": 0, //上一个兄弟节点分类ID
+    "NextClassifyId": 227, //下一个兄弟节点分类ID
+    "ChartInfoId": 0, //图表ID,如果图表ID>0则移动对象为图表,否则默认为移动分类
+    "PrevChartInfoId": 0, //上一个兄弟节点图表ID
+    "NextChartInfoId": 0 //下一个兄弟节点图表ID
    * @returns 
    */
   classifyMove: params => {
@@ -253,15 +270,140 @@ export default{
     return http.post('/datamanage/multiple_graph/preview_cure',params)
   },
 
-  /**
-	 * 设置图表对应版本信息
-	 * @param {*} params 
-	 * ChartInfoId ChartName
-	 * @returns 
-	 */
-	setChartLangInfo: params => {
-		return http.post('/correlation/chart_info/base/edit',params)
-	},
+    /**
+     * 设置图表对应版本信息
+     * @param {*} params 
+     * ChartInfoId ChartName
+     * @returns 
+     */
+    setChartLangInfo: params => {
+        return http.post('/correlation/chart_info/base/edit',params)
+    },
+
+    /* 相关性功能拓展 */
+
+    /**
+     * 获取计算方式列表
+     * @param {Object} params 
+     * @param {Number} params.EdbInfoType  指标类型:0-普通指标;1-预测指标
+     * @returns 
+     */
+    getCalculateFormula:params => {
+        return http.get('/datamanage/factor_edb_series/calculate_func/list',params)
+    },
+    /**
+     * 添加多因子系列
+     * @param {Object} params 
+     * @param {String} params.SeriesName 系列名称
+     * @param {Number} params.EdbInfoType 指标类型:0-普通指标; 1-预测指标
+     * @param {Object[]} params.Calculates 选择的计算公式
+     * @param {String} params.Calculates[].Formula N值/移动天数/指数修匀alpha值/计算公式等
+     * @param {String} params.Calculates[].Calendar 公历/农历
+     * @param {String} params.Calculates[].Frequency 需要转换的频度
+     * @param {String} params.Calculates[].MoveType 移动方式:1-领先(默认);2-滞后
+     * @param {String} params.Calculates[].MoveFrequency 移动频度
+     * @param {String} params.Calculates[].FromFrequency 来源的频度
+     * @param {Number} params.Calculates[].Source 计算方式来源
+     * @param {Number} params.Calculates[].Sort 计算顺序
+     * @param {Number[]} params.EdbInfoIds 需要计算的指标
+     * @returns 
+     */
+    addFactorSeries:params=>{
+        return http.post('/datamanage/factor_edb_series/add',params)
+    },
+    /**
+     * 编辑多因子系列
+     * @param {Object} params 
+     * @param {String} params.SeriesId //因子系列ID
+     * //其他同添加
+     * @returns 
+     */
+    editFactorSeries:params=>{
+        return http.post('/datamanage/factor_edb_series/edit',params)
+    },
+    /**
+     * 获取多因子系列详情
+     * @param {Object} params
+     * @param {Number} params.SeriesId 因子指标系列ID
+     * @returns 
+     */
+    getFactorSeriesDetail:params=>{
+        return http.get('/datamanage/factor_edb_series/detail',params)
+    },
+    /**
+     * 获取相关性矩阵
+     * @param {Object} params
+     * @param {Number} params.BaseEdbInfoId 标的指标ID
+     * @param {Object} params.Correlation
+     * @param {Number} params.Correlation.CalculateValue 计算窗口
+     * @param {String} params.Correlation.CalculateUnit 计算窗口频度
+     * @param {Number} params.Correlation.LeadValue 分析周期
+     * @param {String} params.Correlation.LeadUnit 分析周期频度
+     * @param {Number[]} params.SeriesIds 因子系列Id
+     * @returns 
+     */
+    getCorrelationMatrix:params=>{
+        return http.post('datamanage/factor_edb_series/correlation/matrix',params)
+    },
+    /**
+     * 新增多因子相关性图表
+     * @param {Object} params 
+     * @param {String} params.ChartName
+     * @param {Number} params.ClassifyId
+     * @param {Number} params.AnalysisMode 分析模式:0-单因子;1-多因子
+     * @param {Number} params.BaseEdbInfoId 标的指标ID
+     * @param {Object} params.FactorCorrelation
+     * @param {Number} params.FactorCorrelation.CalculateValue 领先期数
+     * @param {String} params.FactorCorrelation.CalculateUnit 频度
+     * @param {Number} params.FactorCorrelation.LeadValue 计算窗口
+     * @param {String} params.FactorCorrelation.LeadUnit 计算频度
+     * @param {Object[]} params.FactorCorrelation.SeriesEdb 关联系列指标
+     * @param {Number} params.FactorCorrelation.SeriesEdb[].SeriesId
+     * @param {Number} params.FactorCorrelation.SeriesEdb[].EdbInfoId
+     * @param {Object} params.ExtraConfig
+     * @param {Object[]} params.ExtraConfig.LegendConfig 
+     * @param {String} params.ExtraConfig.LegendConfig.LegendName
+     * @param {Number} params.ExtraConfig.LegendConfig.SeriesId
+     * @param {Number} params.ExtraConfig.LegendConfig.EdbInfoId 
+     * @param {String} params.ExtraConfig.LegendConfig.Color 
+     * @param {Object} params.SourcesFrom
+     * @param {Boolean} params.SourcesFrom.isShow
+     * @param {String} params.SourcesFrom.text
+     * @param {String} params.SourcesFrom.color
+     * @param {Number} params.SourcesFrom.fontSize
+     * @returns 
+     */
+    addMultipleFactor:params=>{
+        return http.post('/correlation/chart_info/multi_factor/add',params)
+    },
+    /**
+     * 更新多因子相关性图表
+     * @param {*} params 
+     * @param {Number} params.ChartInfoId
+     * 其他同新增
+     * @returns 
+     */
+    editMultipleFactor:params=>{
+        return http.post('/correlation/chart_info/multi_factor/edit',params)
+    },
+    /**
+     * 多因子图表详情
+     * @param {Object} params 
+     * @param {String} params.UniqueCode
+     * @returns 
+     */
+    getMultipleChartDetail:params=>{
+        return http.get('/datamanage/chart_info/common/detail/from_unique_code',params)
+    },
+    /**
+     * 多因子表单及矩阵详情
+     * @param {*} params
+     * @param {Number} params.UniqueCode 
+     * @returns 
+     */
+    getMultipleFactorDetail:params=>{
+        return http.get('/correlation/chart_info/multi_factor/detail',params)
+    }
 
 }
 

BIN
src/assets/img/icons/formula-add.png


+ 4 - 0
src/lang/commonLang.js

@@ -13,6 +13,10 @@ export default {
       en: "Confirm",
       zh: "确定",
     },
+    calculate_btn:{
+        en:'Calculate',
+        zh:'计算'
+    },
     confirm_save_btn: {
       en: "Save",
       zh: "保存 ",

+ 73 - 2
src/lang/modules/StatisticAnalysis/ChartRelevance.js

@@ -34,7 +34,31 @@ export const ChartRelevanceEn = {
     update_edb:'update',
     update_success:'Update success',
     chart_save_as:'Save Chart As',
-    check_value_hint:'Correlation calculation window must be ≥2* analysis period'
+    check_value_hint:'Correlation calculation window must be ≥2* analysis period',
+
+    analytical_model:'Mode',
+    single_indicator:'Single Indicator',
+    multiple_indicators:'Multiple Indicators',
+    target_indicator:'Target Indicator',
+    factor_indicators:'Factor Indicators',
+    add_factor_indicators:'Add factor indicators',
+
+    multiple_table_head_01:'Correlation matrix with the Target Indicator',
+    multiple_table_head_02:'Original Indicator name',
+    multiple_table_head_03:'Number of leading periods({unit})', //$t('xxx',{unit:''})
+    multiple_table_btn_add:'Add Curve',
+    multiple_table_btn_del:'Del Curve',
+    chart_curve_add_hint:'Only 10 curves are supported',
+    infoform_rules_hint_01:'IndexA not selected',
+    infoform_rules_hint_02:'IndexB not selected',
+    infoform_rules_hint_03:'Target Index not selected',
+    infoform_rules_hint_04:'Calculation Window not filled in',
+    infoform_rules_hint_05:'Analysis Period not filled in',
+    formula_add_btn:'Add Formula',
+    series_name:'Index Series Name',
+    series_name_placeholder:'Input Index Series Name',
+    formulaform_add_hint:'Only 5 formulas are supported',
+    batch_select_hint:'Please Select Index',
 };
   
 /* 中文 */
@@ -70,7 +94,54 @@ export const ChartRelevanceZh = {
     update_success:'更新成功',
     chart_save_as:'图表另存为',
 
-    check_value_hint:'相关性计算窗口必须≥2*分析周期'
+    check_value_hint:'相关性计算窗口必须≥2*分析周期',
+
+    analytical_model:'分析模式',
+    single_indicator:'单因子',
+    multiple_indicators:'多因子',
+    target_indicator:'标的指标',
+    factor_indicators:'因子指标系列',
+    add_factor_indicators:'添加因子指标系列',
+    /**
+     * Chart.ChartType
+     * 曲线图:spline_name
+     * 相关性:correlation_name
+     * 滚动相关性:rolling_correlation_name
+     * Chart
+     * 请选择时间段:choose_time
+     * 
+     * 操作 Edb.Detail.e_opera
+     * 数据来源 Edb.Detail.source
+     * 暂无信息 Common.no_info_msg
+     * 加入已选指标 EtaBasePage.add_to_selections
+     * N不能为空 Edb.Valids.n_msg
+     * alpha值不能为空 Edb.Valids.alpha_msg
+     */
+    multiple_table_head_01:'与标的指标的相关性矩阵',
+    multiple_table_head_02:'原始指标名称',
+    multiple_table_head_03:'领先期数({unit})', //$t('xxx',{unit:''})
+    multiple_table_btn_add:'添加曲线',
+    multiple_table_btn_del:'删除曲线',
+    chart_curve_add_hint:'最多只支持添加10条曲线',
+    infoform_rules_hint_01:'指标A未选择',
+    infoform_rules_hint_02:'指标B未选择',
+    infoform_rules_hint_03:'标的指标未选择',
+    infoform_rules_hint_04:'计算窗口未填写',
+    infoform_rules_hint_05:'分析周期未填写',
+    formula_add_btn:'添加计算公式',
+    series_name:'指标系列名称',
+    series_name_placeholder:'请输入指标系列名称',
+    formulaform_add_hint:'最多仅能添加5个计算公式',
+    batch_select_hint:'请选择需要添加的指标',
+
+
+
+
+
+
+
+
+
 };
   
 /**

+ 11 - 0
src/routes/modules/chartRoutes.js

@@ -381,6 +381,17 @@ export default [
 					pathName_en:"Correlation analysis"
 				}
 			},
+			{
+				path: 'relevancechartEditorV2',
+				name: '编辑图表',
+				component:()=>import('@/views/chartRelevance_manage/relevance/relevanceChartEditorV2.vue'),
+				meta: {
+					name_en:"edit Correlation analysis",
+					pathFrom: "chartrelevance",
+					pathName: "相关性图表",
+					pathName_en:"Correlation analysis"
+				}
+			},
 			{
 				path: 'statisticFeatureList',
 				name: '统计特征',

+ 1 - 0
src/utils/buttonConfig.js

@@ -548,6 +548,7 @@ export const statisticPermission = {
     corrAnalysis_onlyMine:'corrAnalysis:onlyMine',//只看我的
     corrAnalysis_classifyOpt_edit:'corrAnalysis:classifyOpt:edit',//添加/编辑分类
     corrAnalysis_classifyOpt_delete:'corrAnalysis:classifyOpt:delete',//删除分类
+    corrAnalysis_classifyOpt_move:'corrAnalysis:classifyOpt:move',//移动分类
         /*---图表操作栏--- */
     corrAnalysis_del:'corrAnalysis:del',
     corrAnalysis_enNameSetting:'corrAnalysis:enNameSetting',

+ 54 - 14
src/views/chartRelevance_manage/components/chartCard.vue

@@ -18,6 +18,7 @@
               <el-dropdown-item :command="{entryType,scence:'saveOther'}">{{$t('StatisticAnalysis.ChartRelevance.save_other')}}</el-dropdown-item>
             </el-dropdown-menu>
           </el-dropdown>
+          <!-- 保存指标 -->
           <el-button type="primary" v-show="!isEdbAdd" @click="saveEdb(entryType)">{{$t('StatisticAnalysis.ChartRelevance.save_edb')}}</el-button>
         </template>
 
@@ -28,13 +29,17 @@
           @click="updateChartHandle(entryType)" 
           @command="chartCommandHandle"
         >
+          <!-- 更新图表/指标 -->
           {{$t('StatisticAnalysis.ChartRelevance.update_edb')}}
           <el-dropdown-menu slot="dropdown" v-show="isHaveSaveOtherHandle">
+            <!-- 另存为图表/指标 -->
             <el-dropdown-item :command="{entryType,scence:'saveOther'}">{{$t('Table.save_as')}}</el-dropdown-item>
           </el-dropdown-menu>
         </el-dropdown>
+        <!-- 保存图表 -->
         <el-button type="primary" @click="saveChart(entryType)" v-show="!isChartAdd">{{$t('Dialog.confirm_save_btn')}}</el-button>
-
+        <!-- 图例设置 -->
+        <el-button type="primary" plain @click="handleChartSettiing" v-show="isChartSetting">图例设置</el-button>
       </div>
     </div>
     <div class="chartWrapper">
@@ -46,7 +51,7 @@
         :options="options"
         :index="String(entryType)"
         :ref="`chartRef${entryType}`"
-        height="350"
+        :height="height||350"
       />
     </div>
   </div>
@@ -62,32 +67,42 @@ export default {
   mixins: [chartSetMixin],
   computed: {
     isHaveButton() {
-      let routes = ['/relevancechartEditor','/statisticFeatureChartEditor']
+      let routes = ['/relevancechartEditor','/statisticFeatureChartEditor','/relevancechartEditorV2']
       return routes.includes(this.$route.path)
     },
     cardTitle() {
       let title='';
       if(this.$route.path==='/relevancechartEditor') {
         title = [3,4].includes(this.entryType) ? `滚动相关性${this.entryType-2}` : this.entryType===1 ? this.chartInfo.ChartName : ''
-      }else if(this.$route.path==='/statisticFeatureChartEditor') {
+      }else if(this.$route.path==='/relevancechartEditorV2'){
+        if(this.settings.Model===1){
+            title = [3,4].includes(this.entryType) ? `滚动相关性${this.entryType-2}` : this.entryType===1 ? this.chartInfo.ChartName : this.relevanceChartData.ChartName
+        }else{
+            title = this.relevanceChartData.ChartName
+        }
+      }
+      else if(this.$route.path==='/statisticFeatureChartEditor') {
         title = this.chartInfo.ChartName
       }
       return title
     },
     isHaveEdbHandle() { //有指标操作的区域
       //相关性 3,4区域可保存指标 统计图 2,3区域
-      return (([3,4].includes(this.entryType)&&this.$route.path==='/relevancechartEditor')
+      return (([3,4].includes(this.entryType)&&['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path))
         || ([2,3].includes(this.entryType)&&this.$route.path==='/statisticFeatureChartEditor'))
     },
     isHaveSaveOtherHandle() {//有图表另存的区域
         //相关性 1,2区域有另存 统计图 4个区域都有另存
-      return (([1,2].includes(this.entryType) && this.$route.path==='/relevancechartEditor')
+      return (([1,2].includes(this.entryType) && ['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path))
        || (this.$route.path==='/statisticFeatureChartEditor'))
     }
   },
   watch: {
-    data(nval) {
-      this.setRenderChartData(nval)
+    data:{
+        handler(nval) {
+            this.setRenderChartData(nval)
+        },
+        deep:true
     }
   },
   props: {
@@ -108,6 +123,13 @@ export default {
     },
     settings: {
       type: Object
+    },
+    isChartSetting:{ //是否显示图表设置
+        type:Boolean,
+        default:false
+    },
+    height:{
+        type:String
     }
 
   },
@@ -132,6 +154,7 @@ export default {
         case 3: //3相关性 
           this.chartInfo = data.ChartInfo;
           this.relevanceChartData={
+            ChartName:data.ChartInfo.ChartName,
             ChartInfo:data.ChartInfo,
             EdbInfoList:data.EdbInfoList,
             XEdbIdValue:data.XEdbIdValue || data.XDateTimeValue,
@@ -139,12 +162,16 @@ export default {
             YDataList:[
               {
                 Value:data.YDataList[0].Value,
-                Color:'#00f',
-                Name:data.ChartInfo.ChartName,
+                Color:data.YDataList[0].Color||'#00f',
+                Name:data.YDataList[0].Name||data.ChartInfo.ChartName,
                 NameEn:''
               }
             ]
           }
+          if(this.settings.Model===2){
+            this.relevanceChartData.YDataList = data.YDataList
+            //this.relevanceChartData.ChartName = data.ChartInfo.EdbName+'相关性分析(xxx)'
+          }
           this.tableData=data.EdbInfoList||[]
           this.initRelevanceChartData()
           break
@@ -172,6 +199,10 @@ export default {
           break
       }
     },
+    handleChartSettiing(){
+        this.$emit('chartSettingChange',this.relevanceChartData)
+    },
+    
 
     /* 拟合方程曲线 */
     initFittingEquation(data) {
@@ -239,8 +270,17 @@ export default {
         4: 10
       }
       let Source;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         Source = entryType;
+        //多因子参数不一样,这里直接返回
+        if(this.$parent.chartInfo.Model===2){
+            this.$emit('handleEdit',{
+                ChartName:this.chartInfo.ChartName,
+                ClassifyId:this.chartInfo.ClassifyId,
+                type:'edit'
+            })
+            return
+        }
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         Source = sourceMap[entryType]
       }
@@ -251,7 +291,7 @@ export default {
         ...this.settings
       }
       let res = null;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         res = await chartRelevanceApi.saveChart(params)
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         res = await statisticFeatureInterface.saveChart(params);
@@ -273,7 +313,7 @@ export default {
         4: 10
       }
       let Source;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         Source = entryType;
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         Source = sourceMap[entryType]
@@ -286,7 +326,7 @@ export default {
 			}
       
       let res = null;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         res = await chartRelevanceApi.saveEdb(params)
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         res = await statisticFeatureInterface.saveEdb(params);

+ 10 - 2
src/views/chartRelevance_manage/components/explainText.js

@@ -1,6 +1,7 @@
 //相关性分析
 export const chartrelevanceTextArr = [
     `<p style='font-weight:bold;'>相关性计算处理逻辑:</p>
+    <p style='font-weight:bold;'>单因子:</p>
     <p>1、取数:取计算窗口的时间长度,从当前时间往前推移对应的时间长度,取该日期区间,指标A序列值和指标B序列值;</p>
     <p>2、变频:根据指标A和指标B的频度对取出的数据序列做如下处理</p>
     <p>①指标A高频,对指标B升频(线性方程插值法补全数据);</p>
@@ -14,10 +15,14 @@ export const chartrelevanceTextArr = [
     <p>2、分析周期:指标B领先A的期数,如配置参数10个月,表示B领先A -10月、B领先A -9月,...,B领先A 9月、B领先A 10月,每期分别计算相关性值;</p>`,
     `<p style='font-weight:bold;'>滚动相关性配置:</p>
     <p>1、计算窗口:参与计算的时间段长度,从两个指标都有值的日期开始滚动的取计算窗口长度的值进行计算,如配置计算窗口1个月,则2023.7.28的值取2023.6.28~2023.7.28时间段,2023.7.27的值取2023.6.27~2023.7.27时间段;</p>
-    <p>2、B领先A:B指标领先A指标的参数,为0时不领先;</p>`
+    <p>2、B领先A:B指标领先A指标的参数,为0时不领先;</p>
+    <p style='height:20px;'></p>
+    <p style='font-weight:bold;'>多因子:</p>
+    <p>可一次选择多个指标,与标的指标进行相关性计算。每一个因子指标与标的指标的计算逻辑,同单因子模式。</p>`
 ]
 export const chartrelevanceTextArrEn=[
     `<p style='font-weight:bold;'>Instruction for Correlation Calculation Processing Logic:</p>
+    <p style='font-weight:bold;'>Single Indicator:</p>
     <p>1、 Data Retrieval: Determine the time length of the calculation window, and move backwards from the current time by this duration to obtain a range of dates. Retrieve values for indicator A series and indicator B series within this date range.</p>
     <p>2、Frequency Adjustment:</p>
     <p>a. If indicator A is higher frequency, upsample indicator B (use linear equation interpolation to fill in data).</p>
@@ -31,7 +36,10 @@ export const chartrelevanceTextArrEn=[
     <p>2. Analysis Period: The number of periods that indicator B leads ahead of A; for instance, if configured with a parameter of 10 months, it indicates that B leads A by -10 months to +10 months, with correlation values calculated separately for each period.</p>`,
     `<p style='font-weight:bold;'>Rolling Correlation Configuration:</p>
     <p>1. Calculation Window: The length of time involved in calculations starts from when both indicators have available values and rolls forward taking values within the duration of the calculation window for computation. For example, if configured with a 1-month calculation window, then the value for July 28th, 2023 will be taken from June 28th to July 28th, 2023; while the value for July 27th will be taken from June 27th to July 27th.</p>
-    <p>2. B Leads A: The parameter by which indicator B leads ahead of indicator A; when set to 0, there is no lead.</p>`
+    <p>2. B Leads A: The parameter by which indicator B leads ahead of indicator A; when set to 0, there is no lead.</p>
+    <p style='height:20px;'></p>
+    <p style='font-weight:bold;'>Multiple Indicators:</p>
+    <p>Multiple indicators can be selected at a time to calculate the correlation with the target indicator. The calculation logic of each factor index and the target index is the same as the Single Indicato.</p>`
 ]
 
 //拟合方程曲线

+ 111 - 0
src/views/chartRelevance_manage/components/saveChartSetting.vue

@@ -0,0 +1,111 @@
+<template>
+    <el-dialog 
+        :visible.sync="isSettingChartShow"
+        :close-on-click-modal="false"
+        :modal-append-to-body='false'
+        @close="$emit('close')"
+        center width="500px"
+        title="图例设置"
+        custom-class="chart-setting-dialog"
+    >
+        <div class="dialog-content">
+            <el-form 
+                :modal="settingData" 
+                :rules="formRules"
+                label-width="100px"
+                ref="settingForm">
+                <el-form-item label="图表名称">
+                    <el-input v-model="settingData.chartName"></el-input>
+                </el-form-item>
+                <el-form-item label="图例名称" style="margin-bottom: 0;">
+                </el-form-item>
+                <el-form-item label-width="0px">
+                    <div class="option-box">
+                        <div class="option-item" v-for="(data,index) in settingData.YDataList" :key="index">
+                            <el-color-picker
+                                v-model="data.Color"
+                                size="mini"
+                                show-alpha
+                                :predefine="predefineColors"/>
+                            <el-input v-model="data.Name"></el-input>
+                        </div>
+                    </div>
+                </el-form-item>
+                <el-form-item label="数据来源">
+                    <el-input v-model="settingData.SourcesFrom.text"></el-input>
+                    <el-switch v-model="settingData.SourcesFrom.isShow"></el-switch>
+                </el-form-item>
+            </el-form>
+        </div>
+        <div class="dialog-footer">
+            <el-button type="primary" plain @click="$emit('close')">{{$t('Dialog.cancel_btn')}}</el-button>
+            <el-button type="primary" @click="handleSaveChartSetting">{{$t('Dialog.confirm_btn')}}</el-button>
+        </div>
+
+    </el-dialog>
+</template>
+
+<script>
+import { defaultOpts } from '@/utils/defaultOptions';
+export default {
+    props:{
+        isSettingChartShow:{
+            type:Boolean
+        },
+        settingData:{
+            type:Object,
+        }
+    },
+    data() {
+        return {
+            predefineColors: defaultOpts.colors.slice(0, 2),
+            formRules:{},
+        };
+    },
+    methods: {
+        handleSaveChartSetting(){
+            this.$emit('saveChartSetting')
+        },
+
+    },
+};
+</script>
+
+<style lang="scss">
+.chart-setting-dialog{
+    .el-input{
+        width:230px;
+    }
+    .el-color-picker--mini .el-color-picker__trigger {
+        width: 60px;
+        height: 25px;
+        padding: 0;
+        margin:0 12px 0 28px;
+    }
+    .el-color-picker--mini .el-color-picker__mask {
+        width: 60px;
+        height: 25px;
+    }
+    .el-form{
+        display: flex;
+        flex-direction: column;
+        margin:0 auto;
+        .option-box{
+            .option-item{
+                display: flex;
+                align-items: center;
+                margin-bottom:10px;
+                &:last-child{
+                    margin-bottom: 0;
+                }
+            }
+
+        }
+    }
+    
+    .dialog-footer{
+        text-align:center;
+        margin:30px 0 20px 0;
+    }
+}
+</style>

+ 44 - 8
src/views/chartRelevance_manage/components/saveChartTobaseDia.vue

@@ -34,7 +34,8 @@
                 label: 'ChartClassifyName',
                 value: 'ChartClassifyId',
                 children: 'Children',
-                emitPath: false
+                emitPath: false,
+                checkStrictly:isRelevanceChart
               }"
               style="width: 80%"
               :placeholder="$t('Edb.InputHolderAll.input_classify')"
@@ -92,6 +93,9 @@ export default {
 					{ required: true, message: /* '图表分类不能为空' */this.$t('Chart.Vailds.classify_msg'), trigger: 'blur' },
 				],
 			}
+		},
+		isRelevanceChart(){
+			return ['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)&&this.chartData.Source!==1
 		}
 	},
 	data () {
@@ -111,19 +115,31 @@ export default {
       let res = null;
       if(this.chartData.Source===1) {
         res = await dataBaseInterface.chartClassify();
-      }else if(this.$route.path==='/relevancechartEditor'&&this.chartData.Source!==1) {
-        res = await chartRelevanceApi.classifyList();
+      }else if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)&&this.chartData.Source!==1) {
+        res = await chartRelevanceApi.classifyTree();
       }else if(this.$route.path==='/statisticFeatureChartEditor'&&this.chartData.Source!==1) {
         res = await statisticFeatureInterface.classifyList();
       }
 
       if(res.Ret !== 200) return
-      this.filterNodes(res.Data.AllNodes,this.chartData.Source===1?3:1)
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)&&this.chartData.Source!==1){
+        this.filterNodesAll(res.Data.AllNodes)
+      }else{
+        this.filterNodes(res.Data.AllNodes,this.chartData.Source===1?3:1)
+      }
+      
 
 			this.classifyOptions = res.Data.AllNodes || [];
     
 		},
-
+		filterNodesAll(arr){
+			arr.length && arr.forEach(item => {
+				item.Children && item.Children.length && this.filterNodesAll(item.Children)
+				if(!item.Children.length) {
+					delete item.Children
+				}
+			})
+		},
 		// 递归改变第三级目录结构
 		filterNodes(arr,n) {
 			arr.length && arr.forEach(item => {
@@ -147,8 +163,14 @@ export default {
         4: 10
       }
       let Source;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         Source = this.source;
+        //多因子的参数和接口都不一样,这里直接返回
+        if(this.$parent.chartInfo.Model===2){
+            this.$emit('handleSave',{ChartName: name,ClassifyId: classify,type:this.saveScence})
+            return
+        }
+        
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         Source = sourceMap[this.source]
       }
@@ -162,8 +184,22 @@ export default {
       }
 
       let res = null;
-      if(this.$route.path==='/relevancechartEditor') {
-        res = await chartRelevanceApi.saveChart(params)
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
+        //单因子-相关性额外参数
+        let otherParams = this.source===2?{
+            CorrelationExtraConfig:{
+                LegendConfig:this.$parent.chartBatchData.CorrelationData.YDataList.map(i=>{
+                    return {
+                        LegendName:i.Name,
+                        SeriesId:i.SeriesId,
+                        EdbInfoId:i.Id,
+                        Color:i.Color
+                    }
+                })
+            },
+            SourcesFrom:this.$parent.chartBatchData.SourcesFrom
+        }:{}
+        res = await chartRelevanceApi.saveChart({...params,...otherParams})
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         res = await statisticFeatureInterface.saveChart(params);
       } 

+ 2 - 2
src/views/chartRelevance_manage/components/saveEdbToBaseDia.vue

@@ -173,7 +173,7 @@ export default {
         4: 10
       }
       let Source;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         Source = this.source;
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         Source = sourceMap[this.source]
@@ -190,7 +190,7 @@ export default {
 			}
 
 			let res = null;
-      if(this.$route.path==='/relevancechartEditor') {
+      if(['/relevancechartEditor','/relevancechartEditorV2'].includes(this.$route.path)) {
         res = await chartRelevanceApi.saveEdb(params);
       }else if(this.$route.path==='/statisticFeatureChartEditor') {
         res = await statisticFeatureInterface.saveEdb(params);

+ 5 - 0
src/views/chartRelevance_manage/components/selectTarget.vue

@@ -4,6 +4,7 @@
       style="width: 100%"
       v-model="targetType"
       :placeholder="$t('StatisticAnalysis.StatisticFeatureChart.selecr_indicator_pld')"
+      :disabled="isDisabled"
       @change="targetTypeChange"
       v-if="selectStyleType===1&&filter"
     >
@@ -82,6 +83,7 @@
       :filterable="!search_txt"
       remote
       clearable
+      :disabled="isDisabled"
       :placeholder="$t('Edb.InputHolderAll.select_edb_name')"
       :style="`width: ${width}; ${filter?'margin-top: 20px':''}`"
       :remote-method="searchHandle"
@@ -136,6 +138,9 @@ export default {
       },
       width: {
         default:'100%'
+      },
+      isDisabled:{
+        default:false
       }
     },
     watch:{

+ 4 - 1
src/views/chartRelevance_manage/css/index.scss

@@ -146,13 +146,16 @@ $normal-font: 14px;
               text-align: center;
               margin-bottom: 10px;
             }
-            .chart-author {
+            .chart-author,.chart-source {
               font-size: 14px;
               color: #333;
               position: absolute;
               bottom: 0;
               right: 50px;
             }
+            .chart-source{
+                left:50px;
+            }
             .chartWrapper {
               position: relative;
               .range-cont {

+ 10 - 2
src/views/chartRelevance_manage/mixins/classifyMixin.js

@@ -37,6 +37,14 @@ export default {
         let search_obj = this.searchOptions.find(
           (_) => _.ChartInfoId === newval
         );
+        if(this.$route.path==='/chartrelevance'){
+            if(!search_obj) return 
+            // 重置筛选状态
+            this.select_id = newval;
+            this.select_node = search_obj.UniqueCode;
+            this.select_classify = 0;
+            return 
+        }
         let deep_arr = _.cloneDeep(this.treeData);
         // 查找图表的分类父级id
 
@@ -121,8 +129,8 @@ export default {
 				width > 500
 					? 'auto'
 					: width <= 260
-					? 90
-					: 0.7 * width;
+					? 80
+					: 0.4 * width;
 			this.$set(node, 'Nodewidth', label_wid + 'px');
 		},200),
 

+ 210 - 0
src/views/chartRelevance_manage/relevance/components/batchSelectFormula.vue

@@ -0,0 +1,210 @@
+<template>
+    <div class="batch-select-formula-wrap">
+        <el-form :model="formulaForm" :rules="formulaRules" label-width="80px" ref="formulaForm">
+            <div class="formula-list">
+                <div class="list-item" v-for="(item,index) in formulaForm.CalculateStep" :key="index">
+                    <span>
+                        <img @click="deleteFormula(index)" style="width:15px;height:15px;cursor: pointer;" src="~@/assets/img/ai_m/delete.png" alt="">
+                    </span>
+                    <el-select v-model="item.formulaType">
+                        <el-option 
+                            v-for="option in formulaOpt"
+                            :key="option.Source"
+                            :value="option.Source"
+                            :label="option.CalculateName"
+                        />
+                    </el-select>
+                    <div class="form-item-box">
+                        <!-- N期 N等于-->
+                        <el-form-item
+                            v-if="[5,6,7].includes(item.formulaType)"
+                            :label="$t('EtaBasePage.label_n_val')"
+                            :prop="`CalculateStep[${index}].nNum`"
+                            :rules="{required:true,message:$t('Edb.Valids.n_msg'),trigger:'blur'}"
+                        >
+                            <el-input v-model="item.nNum" :placeholder="$t('Edb.InputHolderAll.input_n_value')" type="number"></el-input>
+                        </el-form-item>
+                        <!-- 超季节性 N等于 日历-->
+                        <el-form-item 
+                            v-if="item.formulaType===11"
+                            :label="$t('EtaBasePage.label_n_val')"
+                            :prop="`CalculateStep[${index}].nNum`"
+                            :rules="{required:true,message:$t('Edb.Valids.n_msg'),trigger:'blur'}"
+                        >
+                            <el-input v-model="item.nNum" :placeholder="$t('Edb.InputHolderAll.input_n_value')" type="number"></el-input>
+                        </el-form-item>
+                        <el-form-item 
+                            v-if="item.formulaType===11"
+                            :label="$t('EtaBasePage.label_calendar')"
+                        >
+                            <el-select v-model="item.Calendar">
+                                <el-option :label="$t('Chart.calendar_gre')" value="公历"></el-option>
+                                <el-option :label="$t('Chart.calendar_lunar')" value="农历"></el-option>
+                            </el-select>
+                        </el-form-item>
+                        <!-- 指数修匀 alpha值 -->
+                        <el-form-item
+                            v-if="item.formulaType===15"
+                            :label="$t('EtaBasePage.alpha_value_lable')" 
+                            :prop="`CalculateStep[${index}].alphaValue`"
+                            :rules="[
+                                {required:true,message:$t('Edb.Valids.alpha_msg'),trigger:'blur'},
+                                {validator:validator,trigger:['change','blur']}]">
+                            <el-input v-model.trim="item.alphaValue" style="width:140px" :placeholder="$t('Edb.InputHolderAll.input_alpha_val')" type="number"></el-input>
+                        </el-form-item>
+                    </div>
+                    
+                </div>
+            </div>
+            <div class="tool-box-wrap">
+                <!-- 添加计算公式 -->
+                <div class="add-btn" @click="addFormula">
+                        <img style="width:15px;height:15px;vertical-align: middle;" src="~@/assets/img/icons/formula-add.png" alt="">
+                    {{ $t('StatisticAnalysis.ChartRelevance.formula_add_btn') }}
+                </div>
+                <div class="input-box">
+                    <el-form-item
+                        required
+                        :label="$t('StatisticAnalysis.ChartRelevance.series_name')"
+                        label-width="120px"
+                    >
+                        <el-input v-model.trim="formulaForm.SeriesName" style="width:230px" 
+                        :placeholder="$t('StatisticAnalysis.ChartRelevance.series_name_placeholder')"></el-input>
+                    </el-form-item>
+                </div>
+            </div>
+        </el-form>
+        
+    </div>
+</template>
+
+<script>
+export default {
+    props:{
+        formulaOpt:{
+            type:Array,
+            default:[]
+        },
+        factorData:{
+            type:Object,
+            default:{}
+        }
+    },
+    computed:{
+        //计算方式列表-改为后端获取
+        /* formulaOption(){
+            return [
+                {
+                    value:5,
+                    label:this.$t('Edb.CalculatesAll.on_year'),//同比值
+                },
+                {
+                    value:7,
+                    label:this.$t('Edb.CalculatesAll.differ'),//同差值
+                },
+                {
+                    value:8,
+                    label:this.$t('Edb.CalculatesAll.rule_move_average'),//N期移动均值
+                },
+                {
+                    value:12,
+                    label:this.$t('Edb.CalculatesAll.period_over_period'),//N期环比值
+                },
+                {
+                    value:13,
+                    label:this.$t('Edb.CalculatesAll.period_difference'),//N期环差值
+                },
+                {
+                    value:35,
+                    label:this.$t('Edb.CalculatesAll.super_seasonal'),//超季节性
+                },
+                {
+                    value:72,
+                    label:this.$t('Edb.CalculatesAll.ex_smooth'),//指数修匀
+                },
+            ]
+        } */
+    },
+    watch:{
+        factorData:{
+            handler(newValue){
+                this.formulaForm = _.cloneDeep(newValue)
+            },
+            immediate:true
+        },
+    },
+    data() {
+        return {
+            formulaForm:{
+                CalculateStep:[],
+                SeriesName:''
+            },
+            formulaRules:{},
+        };
+    },
+    methods: {
+        //打开弹窗时,在外层组件调用
+        initFormulaList(){
+            //this.formulaForm.CalculateStep = _.cloneDeep(this.dataFormulaList)
+            this.getFormulaOption()
+        },
+        validator(rule,value,callback){
+            if(Number(value)<=0||Number(value)>=1){
+                callback(new Error(this.$t('Edb.Valids.alpha_value_vaild')))
+            }else{
+                callback()
+            }
+        },
+        addFormula(){
+            this.formulaForm.CalculateStep.push({
+                formulaType:5,
+                Calendar:'公历',
+                nNum:1,
+                alphaValue:0.5
+            })
+        },
+        deleteFormula(index){
+            if(this.formulaForm.CalculateStep.length>=5){
+                return this.$message.warning(/* "最多仅能添加5个计算公式" */ this.$t('StatisticAnalysis.ChartRelevance.formulaform_add_hint'))
+            }
+            this.formulaForm.CalculateStep.splice(index,1)
+        },
+        async checkForm(){
+            return await this.$refs.formulaForm.validate()
+        },
+    },
+};
+</script>
+
+<style lang="scss">
+.batch-select-formula-wrap{
+    .el-input,.el-select{
+        width:140px;
+    }
+    .formula-list{
+        margin-top: 20px;
+        .list-item{
+            margin-bottom: 15px;
+            display: flex;
+            align-items: center;
+            gap:0 15px;
+            .form-item-box{
+                display: flex;
+                .el-form-item{
+                    margin-bottom: 0;
+                }
+            }
+        }
+    }
+    .tool-box-wrap{
+        margin-top: 20px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .add-btn{
+            color:#0052D9;
+            cursor: pointer;
+        }
+    }
+}
+</style>

+ 257 - 0
src/views/chartRelevance_manage/relevance/components/batchSelectTable.vue

@@ -0,0 +1,257 @@
+<template>
+    <!-- 封装表格跨页多选逻辑 -->
+    <div class="batch-select-table">
+        <div class="table-box" style="width:50%;">
+            <el-table
+                border height="500px"
+                v-loading="tableLoading"
+                ref="tableRef" 
+                :data="tableData"
+                @select="selectHandle" 
+                @select-all="selectAllHandle"
+            >
+                <el-table-column type="selection" min-width="50" align="center"/>
+                <el-table-column 
+                    align="center"
+                    v-for="column in tableColumns" 
+                    :key="column.key"
+                    :label="column.label"
+                    :prop="column.key"
+                    :width="column.width"
+                    :show-overflow-tooltip="column.showOverflowTooltip">
+                    <template slot-scope="{row}">
+                        <span v-if="column.key==='Frequency'">{{ getFrequencyTrans(row.Frequency) }}</span>
+                        <span v-else-if="['UniEn','Unit'].includes(column.key)">{{ getUnitTrans(row.Unit) }}</span>
+                        <span v-else>{{ row[column.key] }}</span>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <m-page 
+                style="margin-top:10px"
+                class="table-page" 
+                v-show="tableParams.total"
+                :total="tableParams.total" 
+                :pageSize="tableParams.pageSize"
+                :page_no="tableParams.pageNo"
+                :pagercount="tableParams.pagerCount"
+                @handleCurrentChange="pageNumberChange"
+            />
+        </div>
+        <!-- 加入已选指标 -->
+        <el-button type="primary" @click="handleAddSelectData">{{ $t('EtaBasePage.add_to_selections') }}</el-button>
+        <div class="select-box" style="width: 35%;">
+            <el-table
+                border height="500px"
+                ref="selectRef" 
+                :data="selectData"
+            >
+            <el-table-column
+                align="center"
+                :label="tableColumns[0].label"
+                :prop="tableColumns[0].key"
+             />
+             <el-table-column width="50px" align="center" v-if="selectData.length">
+                <template slot="header" slot-scope="scope">
+                    <img @click="handleDelSelect(_,'all')" style="width:15px;height:15px;cursor: pointer;" src="~@/assets/img/ai_m/delete.png" alt="">
+                </template>
+                <template slot-scope="{row,$index}">
+                    <img @click="handleDelSelect($index,'')" style="width:15px;height:15px;cursor: pointer;" src="~@/assets/img/ai_m/delete.png" alt="">
+                </template>
+            </el-table-column>
+        </el-table>
+        </div>
+    </div>
+</template>
+
+<script>
+import mPage from '@/components/mPage.vue'
+export default {
+    components:{mPage},
+    props:{
+        tableData:{
+            type:Array,
+            default:[]
+        },
+        tableLoading:{
+            type:Boolean,
+            default:false
+        },
+        tableColumns:{
+            type:Array,
+            default:[]
+        },
+        tableParams:{
+            type:Object,
+            default:()=>{
+                return {
+                    total:0,
+                    pageSize:20,
+                    pageNo:1,
+                    pagerCount:5,
+                    uniqueKey:'Id'
+                }
+            }
+        },
+        isSelectAll:{
+            type:Boolean,
+            default:false,
+        },
+        factorData:{
+            type:Object,
+            default:{}
+        }
+    },
+    watch:{
+        /* isSelectAll(newValue){
+            this.listCheckAllChange(newValue)
+        }, */
+        factorData:{
+            handler(newValue){
+                this.selectData = _.cloneDeep(newValue.EdbMappings||[])
+            },
+            immediate:true,
+            deep:true
+        }
+        
+    },
+    data() {
+        return {
+            selectList:[],//左侧表格已选择/已剔除的数据
+            selectData:[],//右侧表格的数据
+            selectionReactCancel:false,
+        };
+    },
+    methods: {
+        //点击数据行的checkbox触发 selection:已选择的所有数据行;row:当前点击的数据行
+        selectHandle(selection,row){
+            const {uniqueKey} = this.tableParams
+            //通过判断selection中有无row确定是勾选了checkbox还是取消勾选
+            //当为勾选时,判断selectList是否表示已选择的数据(isSelectAll===false),若不是则不加入
+            //当为取消勾选时,判断selectList是否表示已剔除的数据(isSelectAll===true),若不是则不加入
+            let check = selection.some(i=>i[uniqueKey] == row[uniqueKey])?(!this.isSelectAll):(this.isSelectAll)
+            if(check){
+                this.selectList.push(row[uniqueKey])
+            }else{
+                this.selectList = this.selectList.filter(i=>i!=row[uniqueKey])
+            }
+            this.selectionChange()
+        },
+        //点击表头全选 or 调用toggleAllSelection 触发
+        selectAllHandle(selection){
+            //当前table的所有数据
+            const tableIds = this.tableData.map(it => it[this.tableParams.uniqueKey])
+            //通过判断selection中有无数据确定是全选or取消全选
+            //当为全选时,判断selectList是否表示已选择的数据(isSelectAll===false),若不是则从selectList中剔除
+            //当为取消全选时,判断selectList是否表示已剔除的数据(isSelectAll===true),若不是则从selectList中剔除
+            let check = selection&&selection.length?(!this.isSelectAll):this.isSelectAll
+            if(check){
+                this.selectList =  [...this.selectList,...tableIds]
+            }else{
+                this.selectList = this.selectList.filter(it => !tableIds.includes(it))
+            }
+            this.selectionChange()
+        },
+        //当勾选项改变时 处理数据
+        selectionChange(){
+            //去重 selectList
+            let duplicateArr = Array.from(new Set(this.selectList))
+            let isCheckAll = false,isIndeterminate = false
+            //判断已选择的数据,更改列表全选checkbox的状态
+            /**
+             * 全选:
+             * 1.selectList.length === total && isSelectAll === false (已选择的数据为全部数据)
+             * 2.selectList.length === 0 && isSelectAll === true (已剔除的指标为空)
+             * 以上两种满足其一即可
+             */
+            const selectAll = duplicateArr.length===this.tableParams.total && (!this.isSelectAll)
+                || duplicateArr.length===0 && this.isSelectAll
+            /**
+             * 全不选:
+             * 1.selectList.length === total && isSelectAll === true (剔除了全部数据)
+             * 2.selectList.length === 0 && isSelectAll === false (没选择任何数据)
+             * 以上两种满足其一即可
+             */
+            const selectNone = duplicateArr.length===this.tableParams.total && this.isSelectAll
+                || duplicateArr.length===0 && (!this.isSelectAll)
+            //其余情况均为半选
+
+            if(selectAll){
+                isCheckAll = true
+                isIndeterminate = false
+            }else if(selectNone){
+                isCheckAll = false
+                isIndeterminate = false
+            }else{
+                isCheckAll = false
+                isIndeterminate = true
+            }
+
+            this.$emit('changeCheckAll',{isCheckAll,isIndeterminate})
+        },
+        //切换页面时调整勾选项 (在外层组件调用)
+        adjustSelection(){
+            const {uniqueKey} = this.tableParams
+            this.$refs.tableRef.clearSelection()
+            this.tableData.forEach(data=>{
+                if(this.selectList.includes(data[uniqueKey])){
+                    //isSelectAll===false,selectList为已选择的数据,则对应数据行打勾
+                    //isSelectAll === true,selectList为已剔除的数据,则对应的数据行取消打勾
+                    this.$nextTick(()=>{
+                        this.$refs.tableRef.toggleRowSelection(data,!this.isSelectAll)
+                    })
+                }else{
+                    this.$nextTick(()=>{
+                        this.$refs.tableRef.toggleRowSelection(data,this.isSelectAll)
+                    })
+                }
+            })
+        },
+        listCheckAllChange(newValue){
+            this.selectList = []
+            this.$refs.tableRef && this.tableData.length && this.$refs.tableRef.clearSelection()
+            if(newValue){
+                //会触发select-all
+                this.$refs.tableRef && this.tableData.length && this.$refs.tableRef.toggleAllSelection()
+                /* //不会触发select-all
+                this.$refs.tableRef && this.tableData.length && this.$refs.tableRef.toggleRowSelection(this.tableData[0],true) */
+            }
+        },
+        pageNumberChange(page){
+            this.$emit('pageChange',page)
+        },
+        handleAddSelectData(){
+            //外层组件处理校验和添加逻辑
+            this.$emit('addSelectData',{selectList:this.selectList,selectData:this.selectData})
+
+        },
+        //外层组件调用
+        addSelectData(data){
+            this.selectData = _.cloneDeep(data)
+            
+        },
+        handleDelSelect(index,type){
+            if(type==='all'){
+                return (this.selectData = [])
+            }
+            this.selectData.splice(index,1)
+        },
+    },
+};
+</script>
+
+<style scoped lang="scss">
+.batch-select-table{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap:0 20px;
+    .table-box,.select-box{
+        flex:1;
+    }
+    &::after{
+        content:'';
+        flex:0 0 auto;
+        visibility: hidden;
+    }
+}
+</style>

+ 38 - 0
src/views/chartRelevance_manage/relevance/components/formMixin.js

@@ -0,0 +1,38 @@
+//单因子,多因子的表单 共同逻辑
+import {yearSelector} from '@/utils/defaultOptions';
+import selectTarget from '../../components/selectTarget.vue'
+export default{
+    components:{selectTarget},
+    props:{
+        chartInfoData:{
+            type:Object,
+            default:{}
+        },
+        infoForm:{
+            type:Object,
+            default:()=>{return {Curve:{},Correlation:{},RollingCorrelation:[]}}
+        }
+    },
+    computed:{
+        yearSelector(){
+            return [
+                ...yearSelector,
+                { name: this.$i18n.locale == 'zh'?'自定义':'custom',value: 5 }
+            ]
+        },
+        dayOpt(){
+            return [
+                {label:this.$t('Edb.FreAll.year_min'),val:'年'},
+                {label:this.$t('Edb.FreAll.quarter_min'),val:'季'},
+                {label:this.$t('Edb.FreAll.month_min'),val:'月'},
+                {label:this.$t('Edb.FreAll.week_min'),val:'周'},
+                {label:this.$t('Edb.FreAll.day_min'),val:'天'},
+            ]
+        }
+    },
+    methods: {
+        handleSelectTarget(type='A',target=''){
+            this.$emit(`selectTarget`,{type,target})
+        },
+    },
+}

+ 137 - 0
src/views/chartRelevance_manage/relevance/components/modifyClassifyDialog.vue

@@ -0,0 +1,137 @@
+<template>
+    <div class="Dialog-box">
+        <el-dialog
+        :visible.sync="isOpenDialog"
+        :close-on-click-modal="false"
+        :modal-append-to-body='false'
+        @close="cancelHandle"
+        custom-class="dialog"
+        center
+        width="560px"
+        v-dialogDrag>
+            <div slot="title" style="display:flex;alignItems:center;">
+                <img :src="type=='add'?$icons.add:$icons.edit" style="color:#fff;width:16px;height:16px;marginRight:5px;">
+                <span style="fontSize:16px;">{{type==='add' ? $t('Table.add_btn') : $t('Table.edit_btn')}}</span>
+            </div>
+            <div class="dialog-main">
+                <el-form
+                ref="diaForm"
+                label-position="left"
+                hide-required-asterisk
+                label-width="120px"
+                :model="formData"
+                :rules="formRules">
+                    <el-form-item :label="$t('OnlineExcelPage.parent_directory_lable')" v-if="formData.level>0">
+                        <el-tooltip class="item" effect="dark" :content="getParentName" placement="top">
+                            <span class="parentStr">{{getParentName}}</span>
+                        </el-tooltip>
+                    </el-form-item>
+                    <el-form-item :label="$t('EtaBasePage.menu_name')" prop="levelVal">
+                        <el-input
+                        v-model="formData.levelVal"
+                        style="width: 80%"
+                        :placeholder="$t('Dialog.require_vaild')"></el-input>
+                    </el-form-item>
+                </el-form>
+            </div>
+            <div class="dia-bot">
+                <el-button type="primary" style="margin-right:20px" @click="saveHandle"><!-- 保存 -->{{$t('Dialog.confirm_save_btn')}}</el-button>
+                <el-button type="primary" plain @click="cancelHandle"><!-- 取消 -->{{$t('Dialog.cancel_btn')}}</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import chartRelevanceApi from "@/api/modules/chartRelevanceApi.js";
+export default {
+    props: {
+        isOpenDialog: {
+            type: Boolean,
+        },
+        type:{
+            type:String,
+            default:'add'
+        },
+        formData: {
+            type: Object,//{parentArr父级数据}
+        }
+    },
+    computed:{
+        getParentName(){
+            const arr=this.formData.parentArr||['无']
+            let strArr=arr.reverse().map(item=>{
+                return item.classifyName
+            })
+            
+            return strArr.join('/')
+        }
+    },
+    data () {
+        return {
+            formRules: {
+                levelVal:[
+                    { required: true, message: this.$t('EtaBasePage.input_menu_msg'), trigger: 'blur' },
+                ],
+            },
+            options:  [],
+
+        };
+    },
+    methods: {
+        async saveHandle() {
+            await this.$refs.diaForm.validate();
+            let addParams = {
+                ChartClassifyName:this.formData.levelVal,
+                Level:this.formData.level,
+                ParentId:this.formData.parent_id,
+            }
+            let editParams = {
+                ChartClassifyName:this.formData.levelVal,
+                ChartClassifyId:this.formData.nodeId
+            }
+            const res = this.type==='add'?await chartRelevanceApi.classifyAdd(addParams):await chartRelevanceApi.classifyEdit(editParams)
+            if(res.Ret!==200) return 
+            this.$message.success(`${this.type==='add'?'新增':'编辑'}成功`)
+            this.callbackHandle()
+
+
+        },
+        /* 成功回调 */
+        callbackHandle(type) {
+            this.$refs.diaForm.resetFields();
+            this.$emit('sucessCallback',type)
+        },
+        /* 取消 */
+        cancelHandle() {
+            this.$refs.diaForm.resetFields();
+            this.$emit('closeDia')
+        },
+    },
+    created() {},
+    mounted() {},
+}
+</script>
+<style lang='scss'>
+.Dialog-box {
+    .parentStr{
+        display: block;
+        width: 304px;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+    }
+    .dialog-main {
+        padding-left: 50px;
+    }
+    .el-cascader .el-input {
+        width: 100%;
+    }
+    .dia-bot {
+        margin: 52px 0 30px;
+        display: flex;
+        justify-content: center;
+
+    }
+    }
+</style>

+ 616 - 0
src/views/chartRelevance_manage/relevance/components/multipleIndForm.vue

@@ -0,0 +1,616 @@
+<template>
+    <div class="multiple-model-form model-form">
+        <!-- 标的指标 -->
+        <el-form-item 
+            :label="$t('StatisticAnalysis.ChartRelevance.target_indicator')" 
+            prop="IndTarget" class="select-target">
+            <!-- 添加后的图表不允许修改标的指标 -->
+            <selectTarget 
+                :isDisabled="isMultipleChartAdd"
+                :defaultId="chartInfoData.EdbInfoList?chartInfoData.EdbInfoList[0].EdbInfoId:''"
+                :defaultOpt="chartInfoData.EdbInfoList?[chartInfoData.EdbInfoList[0]]:[]" 
+                :defaultType="chartInfoData.EdbInfoList?chartInfoData.EdbInfoList[0].EdbInfoCategoryType:''"
+                @select="(target)=>handleSelectTarget('IndTarget',target)"
+            />
+        </el-form-item>
+        <!-- 因子指标系列 -->
+        <div class="factor-form-item">
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.factor_indicators')" required>
+            </el-form-item>
+            <div class="factor-list">
+                    <div class="list-item" 
+                        v-for="(item,index) in factorList" :key="index">
+                        <span>
+                            <!-- <i class="el-icon-arrow-right"></i> -->
+                            {{ item.SeriesName }}
+                        </span>
+                        <span  @click.stop="openAddDialog(item)" style="margin-left: auto;">
+                            <img src="~@/assets/img/icons/edit_blue_new.png" alt="" style="width: 16px; height: 16px; margin-right: 5px">
+                        </span>
+                        <span @click="deleteFactorIndicators(index)">
+                            <img src="~@/assets/img/set_m/del_icon.png" alt="" style="width: 14px; height: 14px;">
+                        </span>
+                    </div>
+            </div>
+            <div class="add-factor-btn" @click="openAddDialog(null)">
+                <img src="~@/assets/img/add-quadrate-blue.png" />
+                {{ $t('StatisticAnalysis.ChartRelevance.add_factor_indicators') }}
+            </div>
+        </div>
+        <div class="form-box">
+            <!-- 相关性 -->
+            <div class="label-title">{{ $t('Chart.ChartType.correlation_name') }}</div>
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.calculation_window')"
+                prop="Correlation.CalculateValue" class="flex-form-item">
+                <el-input
+                    style="flex:2"
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Correlation.CalculateValue"
+                    @change="val => { infoForm.Correlation.CalculateValue = Number(val); }"
+                />
+                <el-select
+                        style="flex:2"
+                        v-model="infoForm.Correlation.CalculateUnit">
+                    <el-option
+                        v-for="item in dayOpt"
+                        :key="item.val"
+                        :label="item.label"
+                        :value="item.val"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.analysis_cycle')" 
+                prop="Correlation.LeadValue" class="flex-form-item">
+                <el-input
+                    style="flex:2"
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Correlation.LeadValue"
+                    @change="val => { infoForm.Correlation.LeadValue = Number(val); }"
+                />
+                <el-select
+                    style="flex:2"
+                    v-model="infoForm.Correlation.LeadUnit">
+                    <el-option
+                        v-for="item in dayOpt"
+                        :key="item.val"
+                        :label="item.label"
+                        :value="item.val"
+                    />
+                </el-select>
+            </el-form-item>
+        </div>
+        <!-- 添加/编辑因子系列弹窗 -->
+        <el-dialog
+            :visible.sync="isAddFactorDialogShow"
+            :close-on-click-modal="false"
+            :modal-append-to-body='false'
+            @close="isAddFactorDialogShow=false"
+            center width="75%" top="30px"
+            :title="$t('StatisticAnalysis.ChartRelevance.add_factor_indicators')"
+            custom-class="add-factor-dialog"
+        >
+            <div class="dialog-content">
+                <!-- 选择指标 or 预测指标 -->
+                <div class="table-radio-wrap">
+                    <el-radio-group v-model="factorData.EdbInfoType" @input="changeEdbType">
+                        <el-radio :label="0">ETA指标</el-radio>
+                        <el-radio :label="1">预测指标</el-radio>
+                    </el-radio-group>
+                    
+                </div>
+                <!-- 表格筛选项 -->
+                <div class="table-select-box">
+                    <el-cascader
+                        v-model="tableSelectParams.classify"
+                        :options="classifyOpt"
+                        :props="{
+                            label: 'ClassifyName',
+                            value: 'ClassifyId',
+                            children: 'Children',
+                            multiple: true,
+                            emitPath:false
+                        }"
+                        clearable collapse-tags
+                        :placeholder="$t('EtaBasePage.label_classify')"
+                        @change="handleFilter"
+                    />
+                    <el-select
+                        v-model="tableSelectParams.frequency"
+                        :placeholder="$t('EtaBasePage.select_frequency')"
+                        clearable multiple collapse-tags
+                        @change="handleFilter">
+                        <el-option
+                            v-for="item in frequencyArr"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        >
+                        </el-option>
+                    </el-select>
+                    <el-cascader
+                        v-model="tableSelectParams.creator"
+                        :placeholder="$t('EtaBasePage.table_col_creator')"
+                        :options="sysUserOpt"
+                        :props="{
+                            value: 'AdminId',
+                            label: 'RealName',
+                            children: 'ChildrenList',
+                            multiple: true,
+                            emitPath:false
+                        }"
+                        collapse-tags
+                        :show-all-levels="false"
+                        clearable filterable
+                        @change="handleFilter"
+                    />
+                    <el-input 
+                        :placeholder="$t('Edb.InputHolderAll.input_name_orid')" 
+                        v-model="tableSelectParams.keyword"
+                        style="width: 240px"
+                        @keydown.enter.native="handleFilter"
+                    >
+                        <i slot="prefix" class="el-input__icon el-icon-search"></i>
+                    </el-input>
+                    <el-checkbox 
+                        :label="$t('EtaBasePage.label_all_check')"
+                        :indeterminate="isIndeterminate" 
+                        v-model="isCheckAll" 
+                        @change="listCheckAllChange"/>
+                </div>
+                <!-- 表格 -->
+                <batchSelectTable
+                    ref="batchSelectTable"
+                    :tableColumns="tableColumns"
+                    :tableData="tableData"
+                    :tableParams="tableParams"
+                    :tableLoading="tableLoading"
+                    :isSelectAll="isSelectAll"
+                    :factorData="factorData"
+                    @pageChange="tablePageChange"
+                    @changeCheckAll="changeCheckAll"
+                    @addSelectData="addSelectData"
+                ></batchSelectTable>
+                <!-- 计算公式 -->
+                <batchSelectFormula
+                    ref="batchSelectFormula"
+                    :factorData="factorData"
+                    :formulaOpt="formulaOpt"
+                ></batchSelectFormula>
+            </div>
+            <div class="dialog-footer">
+                <el-button type="primary" plain @click="isAddFactorDialogShow=false">{{$t('Dialog.cancel_btn')}}</el-button>
+                <el-button type="primary" @click="handleAddFactor" :loading="addFactorLoading">{{$t('Dialog.confirm_btn')}}</el-button>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import batchSelectTable from './batchSelectTable'
+import batchSelectFormula from './batchSelectFormula'
+import formMixin from './formMixin'
+import { frequencySelectList } from '@/utils/defaultOptions'
+import {checkListChange,checkStepsChange} from '../utils/index'
+
+import { dataBaseInterface,departInterence } from '@/api/api.js'
+import * as preDictEdbInterface from "@/api/modules/predictEdbApi.js"
+import chartRelevanceApi from '@/api/modules/chartRelevanceApi'
+export default {
+    mixins:[formMixin],
+	components: { batchSelectTable, batchSelectFormula },
+    props:{
+        SeriesList:{
+            type:Object,
+            default:[]
+        },
+        isMultipleChartAdd:{
+            type:Boolean,
+            default:false
+        }
+    },
+    computed:{
+        tableColumns(){
+            return [
+                {
+                    key:this.$i18nt.locale==='en'?'EdbNameEn':'EdbName',
+                    label:this.$t('EtaBasePage.full_metric_name'),//指标全称
+                    width:'240px',
+                    showOverflowTooltip:true,
+                },
+                {
+                    key:'EndDate',
+                    label:this.$t('Edb.Detail.e_latest_date'),//最新日期
+                    width:'120px',
+                },
+                {
+                    key:'EndValue',
+                    label:this.$t('Edb.Detail.e_latest_value'),//最新值
+                    width:'80px',
+                    showOverflowTooltip:true,
+                },
+                {
+                    key:'SysUserRealName',
+                    label:this.$t('EtaBasePage.table_col_creator'),//创建人
+                    width:'80px',
+                    showOverflowTooltip:true,
+                },
+                {
+                    key:'Frequency',
+                    label:this.$t('Edb.Detail.e_fre'),//频率
+                    width:'50px',
+                    showOverflowTooltip:true,
+                },
+                {
+                    key:this.$i18nt.locale==='en'?'UnitEn':'Unit',
+                    label:this.$t('Edb.Detail.e_unit'),//单位
+                    width:'50px',
+                    showOverflowTooltip:true,
+                },
+            ]
+        },
+        frequencyArr(){
+            return frequencySelectList()
+        },
+    },
+    data(){
+        return {
+            factorList:[], //多因子系列列表
+            isAddFactorDialogShow:false,
+            addFactorLoading:false,
+            factorData:{
+                SeriesName:'',//因子系列名称
+                CalculateStep:[],//因子系列计算公式
+                EdbMappings:[],//因子系列选择的指标
+                EdbInfoType:0,//选择的指标类型:0指标1预测指标
+            },
+            tableData:[],
+            tableSelectParams:{
+                classify:'',
+                frequency:'',
+                creator:'',
+                keyword:'',
+            },
+            classifyOpt:[],
+            sysUserOpt:[],
+            formulaOpt:[],
+            tableParams:{
+                total:0,
+                pageSize:20,
+                pageNo:1,
+                pagerCount:5,
+                uniqueKey:'EdbInfoId',//数据行的唯一key
+            },
+            tableLoading:false,
+            isIndeterminate:false,//与isCheckAll一起表示列表全选的状态
+            isCheckAll:false,//与isIndeterminate一起表示列表全选的状态
+            isSelectAll:false,//是否勾选了列表全选:为true时,selectList是剔除的指标,为false时selectList是已选择的指标
+        }
+    },
+    methods:{
+        openAddDialog(data){
+            if(data){
+                this.factorData = _.cloneDeep(data)
+            }else{
+                this.factorData = {
+                    SeriesName:'',
+                    CalculateStep:[],
+                    EdbMappings:[],
+                    EdbInfoType:0,
+                }
+            }
+            
+            this.getClassifyOpt()
+            this.getSysUserOpt()
+            this.getFormulaOption()
+            //清空筛选项
+            this.initSelectOpt()
+            this.tableData = []
+            this.isAddFactorDialogShow = true
+        },
+        changeEdbType(){
+            if(this.isAddFactorDialogShow){
+                //若切换指标类型,清空选项
+                this.factorData.SeriesName = ''
+                this.factorData.EdbMappings = []
+                this.factorData.CalculateStep =[]
+                this.getFormulaOption()
+                this.getClassifyOpt()
+            }
+        },
+        initSelectOpt(){
+            this.tableSelectParams = {
+                classify:'',
+                frequency:'',
+                creator:'',
+                keyword:'',
+            }
+            this.tableParams = {
+                total:0,
+                pageSize:20,
+                pageNo:1,
+                pagerCount:5,
+                uniqueKey:'EdbInfoId',//数据行的唯一key
+            }
+            this.isIndeterminate = false
+            this.isCheckAll = false
+            this.listCheckAllChange(false)
+        },
+        async getClassifyOpt(){
+            const res= this.factorData.EdbInfoType===0
+                        ?await dataBaseInterface.menuListV3()
+                        :await preDictEdbInterface.classifyListV2()
+            if (res.Ret !== 200) return
+
+            const filterNodes = (arr)=>{
+                arr.length &&
+                    arr.forEach((item) => {
+                        item.Children.length && filterNodes(item.Children);
+                        if (!item.Children.length) {
+                            delete item.Children;
+                        }
+                    });
+            }
+            filterNodes(res.Data.AllNodes||[]);
+
+            this.classifyOpt = res.Data.AllNodes || [];
+        },
+        async getSysUserOpt(){
+            const res = await departInterence.getQuestionAdminList();
+            if (res.Ret === 200) {
+                this.sysUserOpt = res.Data.List||[];
+            }
+        },
+        async getFormulaOption(){
+            const res = await chartRelevanceApi.getCalculateFormula({
+                EdbInfoType:this.factorData.EdbInfoType
+            })
+            if(res.Ret===200){
+                this.formulaOpt = res.Data||[]
+            }
+        },
+        handleFilter(){
+            this.tableParams.pageNo = 1
+            this.tableData = []
+            this.getTableData()
+        },
+        listCheckAllChange(value){
+            this.isSelectAll = value
+            this.$nextTick(()=>{
+                this.$refs.batchSelectTable&&this.$refs.batchSelectTable.listCheckAllChange(value)
+            })
+        },
+        changeCheckAll({isCheckAll,isIndeterminate}){
+            this.isCheckAll = isCheckAll
+            this.isIndeterminate = isIndeterminate
+        },
+        tablePageChange(page){
+            this.tableParams.pageNo = page
+            this.getTableData('pageChange')
+        },
+        async addSelectData({selectList=[],selectData=[]}){
+            //校验:没选择任何指标
+            if(!this.isCheckAll&&!this.isIndeterminate){
+                return this.$message.warning(this.$t('StatisticAnalysis.ChartRelevance.batch_select_hint'))
+            }
+            //通过接口获取选中的所有指标
+            const {classify,frequency,creator,keyword} = this.tableSelectParams
+            const ClassifyIds = classify?classify.join(','):''
+            const Frequency = frequency?frequency.join(','):''
+            const SysUserIds = creator?creator.join(','):''
+            const res=await dataBaseInterface.getBatchFilterAddEdbList({
+                SysUserIds,ClassifyIds,
+                Keyword:keyword,
+                Frequency,
+                SelectAll:this.isSelectAll,
+                EdbInfoIds:selectList.join(','),
+                EdbInfoType:Number(this.factorData.EdbInfoType||0)
+            })
+            if(res.Ret!==200) return 
+            //去重
+            //const data = this.mergeAndDistinct(selectData,res.Data.SearchItem||[])
+            const data = selectData.concat(res.Data.SearchItem||[]).filter((item,index,self)=>{
+                return index ===self.findIndex(t=>t.EdbInfoId === item.EdbInfoId)
+            })||[]
+            //加入到selectData中
+            this.$refs.batchSelectTable.addSelectData(data)
+            //自动填写指标系列名称
+            if(!this.$refs.batchSelectFormula.formulaForm.SeriesName){
+                this.$refs.batchSelectFormula.formulaForm.SeriesName = selectData[0]?selectData.EdbName:data[0]?data[0].EdbName:''
+            }
+        },
+        async getTableData(type){
+            this.tableLoading=true
+            const {classify,frequency,creator,keyword} = this.tableSelectParams
+            const ClassifyIds = classify?classify.join(','):''
+            const Frequency = frequency?frequency.join(','):''
+            const SysUserIds = creator?creator.join(','):''
+            //没有任何筛选项时不展示数据
+            if(!ClassifyIds&&!Frequency&&!SysUserIds&&!keyword){
+                this.tableLoading = false
+                this.tableData = []
+                this.tableParams.total = 0
+                this.isIndeterminate = false
+                this.isCheckAll = false
+                return 
+            }
+            const res=await dataBaseInterface.getBatchAddEdbSearchList({
+                CurrentIndex:this.tableParams.pageNo,
+                PageSize: this.tableParams.pageSize,
+                SysUserIds,ClassifyIds,Frequency,
+                Keyword:keyword,
+                NotFrequency:'',
+                EdbInfoType:Number(this.factorData.EdbInfoType||0)
+            })
+            this.tableLoading=false
+            if(res.Ret!==200) return 
+
+            this.tableData=res.Data.SearchItem||[]
+            this.tableParams.total=res.Data.Paging.Totals||0
+
+            if(type==='pageChange'){
+                this.$nextTick(()=>{
+                    this.$refs.batchSelectTable.adjustSelection()
+                })
+            }else{
+                this.listCheckAllChange(true)
+            }
+        },
+        async handleAddFactor(){
+            //校验 是否选择了指标
+            //校验 计算公式填写
+            await this.$refs.batchSelectFormula.checkForm()
+            //是否超过100个指标
+            if(!this.checkLimitFactor()){
+                return this.$message.warning('所有因子指标已超过100个,请检查')
+            }
+            this.addFactorIndicators()
+        },
+        checkLimitFactor(){
+            let total = 0
+            //已添加过的因子系列
+            this.factorList.forEach(i=>total += i.EdbMappings.length)
+            //当前要添加的因子系列
+            const selectData = this.$refs.batchSelectTable.selectData
+            if(!this.factorData.SeriesId){
+                total+=selectData.length
+            }else{
+                const currentFactor = this.factorList.find(i=>i.SeriesId===this.factorData.SeriesId)
+                const EdbNum = (currentFactor&&currentFactor.EdbMappings.length)||0
+                total -=EdbNum
+                total+=selectData.length
+            }
+            return total<=100
+        },
+        //格式化因子系列计算方式的格式
+        formattingStep(steps=[],isTrans=true){
+            return isTrans
+                ?steps.map((step,index)=>{ //接口需要的格式
+                    const {formulaType,nNum,alphaValue,Calendar} = step
+                    return {
+                        Sort:index+1,
+                        Source:formulaType,
+                        Formula:(formulaType===15?alphaValue:nNum)+'', //N值取nNum,alpha值取alphaValue
+                        Calendar:formulaType===11?Calendar:'',//超季节性取值,其他为空
+                    }
+                })
+                :steps.map(step=>{ //回显需要的格式
+                    const {Source,Calendar,Formula} = step
+                    return {
+                        formulaType:Source,
+                        nNum:Source===15?1:Formula,
+                        alphaValue:Source===15?Formula:0.5,
+                        Calendar
+                    }
+                })
+        },
+        //格式化因子系列所选指标的格式
+        formattingData(data=[],isTrans=true){
+            return isTrans
+                ?data.map(item=>item.EdbInfoId)
+                :data
+        },
+        async addFactorIndicators(){
+            this.addFactorLoading = true
+            //因子指标系列push
+            const selectData = this.$refs.batchSelectTable.selectData
+            const {SeriesName,CalculateStep} = this.$refs.batchSelectFormula.formulaForm
+            const checkList = checkListChange(
+                this.formattingData(selectData),
+                this.formattingData(this.factorData.EdbMappings),
+            )
+            const checkSteps = checkStepsChange(
+                this.formattingStep(CalculateStep),
+                this.formattingStep(this.factorData.CalculateStep),
+            )
+            let factor = {
+                SeriesName,
+                EdbInfoType:this.factorData.EdbInfoType,
+                Calculates:this.formattingStep(CalculateStep),
+                EdbInfoIds:this.formattingData(selectData),
+                Recalculate:checkList||checkSteps,//是否需要重新计算
+            }
+            //添加 Recalculate必为true
+            //编辑 取factor.Recalculate
+            console.log('check list',checkList)
+            console.log('check steps',checkSteps)
+            const res = this.factorData.SeriesId
+                ?await chartRelevanceApi.editFactorSeries({...factor,SeriesId:this.factorData.SeriesId})
+                :await chartRelevanceApi.addFactorSeries({...factor,Recalculate:true})
+            //计算中...
+            this.addFactorLoading = false
+            if(res.Ret!==200) return 
+            //标记计算失败的指标
+            const calculatFailList = res.Data.Fail
+            const baseEdbMappings = selectData
+            //更新factorList
+            if(!this.factorData.SeriesId){
+                this.factorList.push({
+                    SeriesId:res.Data.SeriesId,//接口返的
+                    SeriesName,
+                    EdbInfoType:this.factorData.EdbInfoType,
+                    CalculateStep:CalculateStep,
+                    EdbMappings:selectData,
+                })
+            }else{
+                const index = this.factorList.findIndex(i=>i.SeriesId===this.factorData.SeriesId)
+                this.factorList.splice(index,1,{
+                    SeriesId:this.factorData.SeriesId,
+                    SeriesName,
+                    EdbInfoType:this.factorData.EdbInfoType,
+                    CalculateStep:CalculateStep,
+                    EdbMappings:selectData,
+                })
+            }
+            //如果需要重新计算 抛出事件外层组件处理
+            if(!this.factorData.SeriesId||factor.Recalculate){
+                this.$emit('checkRecalculate')
+            }
+            this.isAddFactorDialogShow = false
+        },
+        deleteFactorIndicators(index){
+            this.factorList.splice(index,1)
+            this.$emit('checkRecalculate')
+        }
+    },
+    mounted(){
+        this.factorList = this.SeriesList.map(i=>{
+            return {
+                SeriesId:i.SeriesId,
+                SeriesName:i.SeriesName,
+                CalculateStep:this.formattingStep(i.CalculateStep||[],false),
+                EdbMappings:i.EdbMappings||[],
+                EdbInfoType:i.EdbInfoType,
+            }
+        })
+    },
+};
+</script>
+
+<style lang="scss">
+.add-factor-dialog{
+    .dialog-content{
+        height: 70%;
+        overflow-y: auto;
+        .table-select-box{
+            margin-bottom: 15px;
+            display: flex;
+            align-items: center;
+            gap:0 15px;
+            .el-cascader,.el-select{
+                .el-input{
+                    width:100%;
+                }
+            }
+        }
+    }
+    .dialog-footer{
+        text-align:center;
+        margin:30px 0 20px 0;
+    }
+}
+</style>

+ 268 - 0
src/views/chartRelevance_manage/relevance/components/singleIndForm.vue

@@ -0,0 +1,268 @@
+<template>
+    <div class="single-model-form model-form">
+        <!-- 指标A -->
+        <el-form-item 
+            :label="$t('StatisticAnalysis.ChartRelevance.edbTagA')" 
+            prop="EdbInfoIdA" class="select-target">
+            <selectTarget  
+                :defaultId="chartInfoData.EdbInfoList?chartInfoData.EdbInfoList[0].EdbInfoId:''"
+                :defaultOpt="chartInfoData.EdbInfoList?[chartInfoData.EdbInfoList[0]]:[]"
+                :defaultType="chartInfoData.EdbInfoList?chartInfoData.EdbInfoList[0].EdbInfoCategoryType:''"
+                @select="(target)=>handleSelectTarget('EdbInfoIdA',target)"
+            />
+        </el-form-item>
+        <!-- 指标B -->
+        <el-form-item 
+            :label="$t('StatisticAnalysis.ChartRelevance.edbTagB')" 
+            prop="EdbInfoIdB" class="select-target">
+            <selectTarget 
+                :defaultId="chartInfoData.EdbInfoList?chartInfoData.EdbInfoList[1].EdbInfoId:''"
+                :defaultOpt="chartInfoData.EdbInfoList?[chartInfoData.EdbInfoList[1]]:[]" 
+                :defaultType="chartInfoData.EdbInfoList?chartInfoData.EdbInfoList[1].EdbInfoCategoryType:''"
+                @select="(target)=>handleSelectTarget('EdbInfoIdB',target)"
+            />
+        </el-form-item>
+        <!-- 曲线图 -->
+        <div class="form-box">
+            <div class="label-title">{{ $t('Chart.ChartType.spline_name') }}</div>
+            <!-- 时间 -->
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.time')" 
+                class="flex-form-item">
+                <!-- 时间类型 -->
+                <el-select
+                    style="width:100%"
+                    v-model="infoForm.Curve.DateType"
+                    @change="getPreviewSplineChart"
+                >
+                    <el-option
+                        v-for="item in yearSelector"
+                        :key="item.value"
+                        :label="item.name"
+                        :value="item.value"
+                    />
+                </el-select>
+                <!-- 时间段 -->
+                <date-picker
+                    v-model="infoForm.Curve.Date"
+                    v-show="infoForm.Curve.DateType===5"
+                    style="margin-left:10px;"
+                    type="month"
+                    range
+                    value-type="format"
+                    :placeholder="$t('Chart.choose_time')"
+                    @change="dateChange"
+                />
+            </el-form-item>
+            <!-- 左轴 -->
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.left_axis')" 
+                class="flex-form-item">
+                <el-input
+                    style=""
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Curve.LeftMin"
+                    @change="val => { infoForm.Curve.LeftMin=Number(val);changeSplineOption() }"
+                />
+                <span>{{ $t('StatisticAnalysis.ChartRelevance.to') }}</span>
+                <el-input
+                    style=""
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Curve.LeftMax"
+                    @change="val => { infoForm.Curve.LeftMax=Number(val);changeSplineOption() }"
+                />
+            </el-form-item>
+            <!-- 右轴 -->
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.right_axis')" 
+                class="flex-form-item">
+                <el-input
+                    style=""
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Curve.RightMin"
+                    @change="val => { infoForm.Curve.RightMin=Number(val);changeSplineOption() }"
+                />
+                <span>{{ $t('StatisticAnalysis.ChartRelevance.to') }}</span>
+                <el-input
+                    style=""
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Curve.RightMax"
+                    @change="val => { infoForm.Curve.RightMax=Number(val);changeSplineOption() }"
+                />
+            </el-form-item>
+            <!-- 指标B -->
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.edbTagB')" >
+                <el-checkbox v-model="infoForm.Curve.IsOrder"  @change="getPreviewSplineChart" style="width:100%">
+                    {{ $t('StatisticAnalysis.ChartRelevance.reverse_sequence') }}
+                </el-checkbox>
+                <div class="edb-type-box">
+                    <el-radio
+                        v-model="infoForm.Curve.EdbInfoType"
+                        :label="true"
+                        style="margin-right:10px;"
+                        @change="getPreviewSplineChart">
+                        {{ $t('StatisticAnalysis.ChartRelevance.standard_index') }}
+                    </el-radio>
+                    <el-radio
+                        v-model="infoForm.Curve.EdbInfoType"
+                        :label="false"
+                        style="margin-right:10px;"
+                        @change="getPreviewSplineChart">
+                        {{ $t('StatisticAnalysis.ChartRelevance.leading_indicator') }}
+                    </el-radio>
+                </div>
+                <div class="edb-type-lead-box" style="text-align: right;" v-if="!infoForm.Curve.EdbInfoType">
+                    <span>{{ $t('StatisticAnalysis.ChartRelevance.lead_tag') }}</span>
+                    <el-input
+                        style="width: 60px"
+                        size="mini"
+                        type="number"
+                        v-model="infoForm.Curve.LeadValue"
+                        @change="(val) => { infoForm.Curve.LeadValue = Number(val);getPreviewSplineChart()}"
+                    ></el-input>
+                    <el-select
+                        v-model="infoForm.Curve.LeadUnit"
+                        placeholder=""
+                        size="mini"
+                        style="width: 60px"
+                        @change="getPreviewSplineChart">
+                        <el-option
+                            v-for="item in dayOpt"
+                            :key="item.val"
+                            :label="item.label"
+                            :value="item.val"
+                        />
+                    </el-select>
+                </div>
+            </el-form-item>
+        </div>
+        <!-- 相关性 -->
+        <div class="form-box">
+            <div class="label-title">{{ $t('Chart.ChartType.correlation_name') }}</div>
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.calculation_window')" 
+                prop="Correlation.CalculateValue" class="flex-form-item">
+                <el-input
+                    style="flex:2"
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Correlation.CalculateValue"
+                    @change="val => { infoForm.Correlation.CalculateValue = Number(val); }"
+                />
+                <el-select
+                        style="flex:2"
+                        v-model="infoForm.Correlation.CalculateUnit"
+                >
+                    <el-option
+                        v-for="item in dayOpt"
+                        :key="item.val"
+                        :label="item.label"
+                        :value="item.val"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.analysis_cycle')" 
+                prop="Correlation.LeadValue" class="flex-form-item">
+                <el-input
+                    style="flex:2"
+                    :step="1"
+                    type="number"
+                    v-model="infoForm.Correlation.LeadValue"
+                    @change="val => { infoForm.Correlation.LeadValue = Number(val); }"
+                />
+                <el-select
+                    style="flex:2"
+                    v-model="infoForm.Correlation.LeadUnit">
+                    <el-option
+                        v-for="item in dayOpt"
+                        :key="item.val"
+                        :label="item.label"
+                        :value="item.val"
+                    />
+                </el-select>
+            </el-form-item>
+        </div>
+        <!-- 滚动相关性 -->
+        <div class="form-box" v-for="(item,index) in infoForm.RollingCorrelation" :key="index">
+            <div class="label-title">{{  $t('Chart.ChartType.rolling_correlation_name') }} {{ index+1 }}</div>
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.calculation_window')" 
+                class="flex-form-item">
+                <el-input
+                    style="flex:2"
+                    :step="1"
+                    type="number"
+                    v-model="item.CalculateValue"
+                    @change="val => { item.CalculateValue = Number(val); }"
+                />
+                <el-select
+                    style="flex:2"
+                    v-model="item.CalculateUnit">
+                    <el-option
+                        v-for="item in dayOpt"
+                        :key="item.val"
+                        :label="item.label"
+                        :value="item.val"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item 
+                :label="$t('StatisticAnalysis.ChartRelevance.B_leads_A')" 
+                class="flex-form-item">
+                <el-input
+                    style="flex:2"
+                    :step="1"
+                    type="number"
+                    v-model="item.LeadValue"
+                    @change="val => { item.LeadValue = Number(val); }"
+                />
+                <el-select
+                    style="flex:2"
+                    v-model="item.LeadUnit">
+                    <el-option
+                        v-for="item in dayOpt"
+                        :key="item.val"
+                        :label="item.label"
+                        :value="item.val"
+                    />
+                </el-select>
+            </el-form-item>
+        </div>
+    </div>
+</template>
+
+<script>
+import formMixin from './formMixin';
+export default {
+    mixins:[formMixin],
+    methods:{
+        dateChange(val){
+            this.infoForm.Curve.DateType = 5
+            if(val[0]) {
+                this.infoForm.Curve.StartDate = val[0];
+                this.infoForm.Curve.EndDate = val[1];
+            }else {
+                this.infoForm.Curve.StartDate = '';
+                this.infoForm.Curve.EndDate = '';
+            }
+            this.getPreviewSplineChart()
+        },
+        getPreviewSplineChart(){
+            this.$emit('previewChart')
+        },
+        changeSplineOption(){
+            this.$emit('changeSpline')
+        },
+    }
+};
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 294 - 23
src/views/chartRelevance_manage/relevance/list.vue

@@ -68,8 +68,9 @@
             :default-expanded-keys="defaultShowNodes"
             draggable
             :expand-on-click-node="false"
-            check-strictly
             empty-text="暂无分类"
+            lazy
+            :load="getLazyTreeData"
             @node-expand="handleNodeExpand"
             @node-collapse="handleNodeCollapse"
             @current-change="nodeChange"
@@ -95,6 +96,7 @@
                 :style="`width:${
                   (select_node === data.UniqueCode && node.Nodewidth) || ''
                 }`"
+                :id="`node${data.UniqueCode}`"
               >
                 <span>{{ currentLang==='en' ? (data.ChartClassifyNameEn||data.ChartClassifyName) : data.ChartClassifyName  }}</span>
               </span>
@@ -106,6 +108,15 @@
                   src="~@/assets/img/data_m/move_ico.png"
                   alt=""
                   style="width: 14px; height: 14px; margin-right: 8px"
+                  v-if="permissionBtn.isShowBtn('statisticPermission','corrAnalysis_classifyOpt_move')"
+                />
+                <!-- 添加子项 -->
+                <img
+                    src="~@/assets/img/set_m/add.png"
+                    alt=""
+                    style="width: 14px; height: 14px; margin-right: 8px"
+                    @click.stop="addNode(node,data)"
+                    v-if="data.Button.AddButton&&node.level<6"
                 />
                 <img
                   src="~@/assets/img/set_m/edit.png"
@@ -167,6 +178,9 @@
                     ref="chartRef"
                   />
                 </div>
+                <span class="chart-source" v-if="chartInfo.SourcesFrom.isShow&&chartInfo.SourcesFrom.text">
+                    数据来源:{{ chartInfo.SourcesFrom.text }}
+                </span>
                 <span class="chart-author"
                   >{{$t('MsgPrompt.author')}}:{{ chartInfo.SysUserRealName }}</span
                 >
@@ -262,7 +276,7 @@
           :total="chart_total" 
           :list="chartList" 
           @loadMoreHandle="loadMoreHandle"
-          @detailShowHandle="detailShowHandle"
+          @detailShowHandle="handleShowChartDetail"
           @addMychartHandle="addMychartHandle"
           ref="chartListWrap"
         />
@@ -270,11 +284,18 @@
     </div>
 
     <!-- 分类弹窗 -->
-    <classify-dia
+    <!-- <classify-dia
       :isOpenDialog.sync="classifyDia"
       :title="dialog_title"
       :form="classifyForm"
       @successCallback="getTreeData"
+    /> -->
+    <modifyClassifyDialog
+      :isOpenDialog.sync="classifyDia"
+      :type="classifyDiaType"
+      :formData="classifyForm"
+      @sucessCallback="sucessCallback"
+      @closeDia="classifyDia = false"
     />
 
     <!-- 加入我的图库弹窗 -->
@@ -319,12 +340,14 @@ import leftMixin from "../mixins/classifyMixin";
 import Chart from "@/views/dataEntry_manage/components/chart";
 import changeLang from "@/views/dataEntry_manage/components/changeLang.vue";
 import classifyDia from "@/views/datasheet_manage/components/sheetClassifyDia.vue";
+import modifyClassifyDialog from './components/modifyClassifyDialog.vue'
 import addMyClassifyDia from "@/views/dataEntry_manage/components/addMyClassifyDia";
 import SaveChartOther from "@/views/dataEntry_manage/components/SaveChartOther";
 import setEnNameDia from "@/views/dataEntry_manage/components/setEnNameDia.vue";
 import { chartSetMixin } from "@/views/dataEntry_manage/mixins/chartPublic";
 import { copyOtherOptions } from '@/utils/defaultOptions';
 import setLangInfoDia from '@/views/dataEntry_manage/components/setLangInfo.vue'
+import { baseSourcesFrom } from './utils/config'
 export default {
   components: {
     changeLang,
@@ -333,7 +356,8 @@ export default {
     addMyClassifyDia,
     SaveChartOther,
     setEnNameDia,
-    setLangInfoDia
+    setLangInfoDia,
+    modifyClassifyDialog
   },
   mixins: [leftMixin, chartSetMixin],
   computed: {
@@ -359,11 +383,12 @@ export default {
       defaultProp: {
         label: "ChartClassifyName",
         children: "Children",
+        isLeaf:'isLeaf'
       }, //树结构配置项
       dynamicNode: null,
 
       /* 分类弹窗 */
-      dialog_title: "",
+      classifyDiaType: "add",//add or edit
       classifyDia: false, //
       classifyForm: {},
 
@@ -392,10 +417,196 @@ export default {
     };
   },
   methods: {
+    /* 判断节点是否能被拖拽 */
+    canDragHandle({data}) {
+        return data.Button.MoveButton&&this.permissionBtn.isShowBtn('statisticPermission','corrAnalysis_classifyOpt_move');
+    },
+    /* 判断节点是否能被拖入 */
+    canDropHandle(draggingNode, dropNode, type) {
+        let canDrop=false
+        
+        // 如果拖动的是图表
+        if(draggingNode.data.ChartInfoId){
+            if(!(dropNode.level===1&&type!=='inner')){
+                canDrop=true
+            }
+        }else{//拖动的是目录
+            //目录层级不能改变
+            if((dropNode.level+1==draggingNode.level&&type==='inner'&&!dropNode.data.ChartInfoId)||(dropNode.level===draggingNode.level&&type!=='inner')){
+                canDrop=true
+            }
+        }
+        return canDrop
+    },
+    /* 拖拽完成 */
+    dropOverHandle(b,a,i,e) {
+        // 被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置
+        const isChart=b.data.ChartInfoId?true:false
+        let list=a.parent.childNodes;
+        let targetIndex=0
+        let ClassifyId=0,//分类ID,若当前节点为图表,则为0
+        ParentClassifyId=0,//移动后,所在位置的父级分类ID
+        PrevClassifyId=0,//前一个节点的分类ID,若前一个节点为图表,则为0
+        NextClassifyId=0,//后一个节点的分类ID,同上
+        ChartInfoId=0,//图表ID,若当前节点为分类,则为0
+        PrevChartInfoId=0,//前一个节点的图表ID,若前一个节点为分类,则为0
+        NextChartInfoId=0;//后一个节点的图表ID
+
+        ClassifyId=isChart?0:b.data.ChartClassifyId
+        ChartInfoId=isChart?b.data.ChartInfoId:0
+        
+
+        if(i!=='inner'){
+            ParentClassifyId=a.parent.data.ChartClassifyId||0
+            list.forEach((item,index)=>{
+                if(isChart){
+                    if(item.data.ChartInfoId===b.data.ChartInfoId){
+                        targetIndex=index
+                    }
+                }else{
+                    if(item.data.ChartClassifyId===b.data.ChartClassifyId){
+                        targetIndex=index
+                    }
+                }
+            })
+
+            console.log(targetIndex);
+            
+            
+            if(targetIndex===0){
+                const data=list[targetIndex+1].data
+                NextClassifyId=data.ChartInfoId?0:data.ChartClassifyId
+                NextChartInfoId=data.ChartInfoId?data.ChartInfoId:0
+            }else if(targetIndex===list.length-1){
+                const data=list[targetIndex-1].data
+                PrevClassifyId=data.ChartInfoId?0:data.ChartClassifyId
+                PrevChartInfoId=data.ChartInfoId?data.ChartInfoId:0
+            }else{
+                const pData=list[targetIndex-1].data
+                PrevClassifyId=pData.ChartInfoId?0:pData.ChartClassifyId
+                PrevChartInfoId=pData.ChartInfoId?pData.ChartInfoId:0
+
+                const nData=list[targetIndex+1].data
+                NextClassifyId=nData.ChartInfoId?0:nData.ChartClassifyId
+                NextChartInfoId=nData.ChartInfoId?nData.ChartInfoId:0
+            }
+        }else{
+            ParentClassifyId=a.data.ChartClassifyId||0
+        }
+
+        const params={
+            ClassifyId,
+            ParentClassifyId,
+            PrevClassifyId,
+            NextClassifyId,
+            ChartInfoId,
+            PrevChartInfoId,
+            NextChartInfoId
+        }
+        console.log(params);
+        chartRelevanceApi.classifyMove(params).then(res=>{
+            if(res.Ret===200){
+                // this.$message.success('移动成功!')
+                this.$message.success(this.$t('MsgPrompt.move_sort_success'))
+            }
+            this.getTreeData()
+            if(this.select_id){
+                this.getDetailHandle();
+            }
+            
+        })
+    },
+
+    /* 拖拽覆盖添加背景色 */
+    dropMouseOver(node1,node2,e) {
+        // console.log(e.layerY);
+        
+        // 被拖拽节点对应的 Node、所进入节点对应的 Node、event
+        if(!node2.data.EdbInfoId&&(node1.level>node2.level||(node1.data.EdbInfoId>0&&!node2.data.EdbInfoId)) && (e.target.childNodes[0].className.includes('el-tree-node__content') 
+        || e.target.className.includes('el-tree-node__content'))) {
+            // console.log(e.target.childNodes[0])
+            e.target.childNodes[0].className.includes('el-tree-node__content') 
+            ? e.target.childNodes[0].style.backgroundColor = '#409eff' 
+            : e.target.style.backgroundColor = '#409eff';
+        }
+        
+    },
+    /* 拖拽离开/拖拽完成重置背景色 */
+    dropMouseLeave(node1,node2,e) {
+        let arrs = $('.el-tree-node__content');
+        for( let a of arrs ) {
+            a.style.backgroundColor = 'transparent';
+        }
+    },
+    sucessCallback(type){
+        this.classifyDia = false;
+        this.getTreeData();
+        if(type === 'add') {
+            //新增分类完成之后,展开父节点显示刚新增的分类,若已展开节点则不做处理
+            let code = sessionStorage.getItem('expandCode');
+            let flag = 	this.defaultShowNodes.some((item) => {
+                return item === code
+            });
+            !flag &&code&& this.defaultShowNodes.push(code);
+            sessionStorage.removeItem('expandCode');
+        }
+    },
+    // 递归节点
+    getNodeParentData(data,arr){
+        if(data.level===0) return
+        arr.push({classifyName:this.currentLang==='en'?data.data.ChartClassifyNameEn:data.data.ChartClassifyName,classifyId:data.data.ChartClassifyId})
+        this.getNodeParentData(data.parent,arr)
+        return arr
+    },
+    addNode(node,data){
+        this.classifyDiaType = 'add';
+        let arr=[]
+        arr=this.getNodeParentData(node,arr)
+        /* 添加目录 */
+        this.classifyForm = {
+            parentArr:arr,
+            parent_id: data.ChartClassifyId,
+            level: node.level,
+            levelVal:'',
+            nodeId:'',
+        }
+        //存储当前要新增子级的目录code
+        sessionStorage.setItem('expandCode', data.UniqueCode);
+        this.classifyDia = true;
+    },
+    //绑定el-tree的load属性
+    async getLazyTreeData (node,resolve){
+        if(node.level===0){
+            resolve(this.treeData)
+        }else{
+            let arr=[]
+            const res=await chartRelevanceApi.classifyList({
+                ParentId:node.data.ChartClassifyId,
+                IsShowMe:this.isOnlyMe,
+                Source: this.classify_tab ? 4 : 3})
+            if (res.Ret === 200) {
+                const temarr = res.Data.AllNodes || [];
+                arr=temarr.map(item=>{
+                    return {
+                        ...item,
+                        isLeaf:item.ChartInfoId?true:false
+                    }
+                })
+            }
+            resolve(arr)
+        }
+    },
+    //展开选中图表父级,选中选择图表
+    handleShowChartDetail({ UniqueCode, ChartInfoId }){
+        this.select_classify = 0;
+        this.select_id = ChartInfoId;
+        this.select_node = UniqueCode;
+    },
     /* 添加图表 */
     goAddChart() {
       if (!this.treeData.length) return this.$message.warning("请先添加分类");
-      this.$router.push({ path: "/relevancechartEditor" });
+      //this.$router.push({ path: "/relevancechartEditor" });
+      this.$router.push({ path: "/relevancechartEditorV2" });
     },
 
     /* 获取分类 */
@@ -410,10 +621,20 @@ export default {
           if (Ret !== 200) return;
 
           this.showData = true;
-          this.treeData = Data.AllNodes || [];
+          //this.treeData = Data.AllNodes || [];
+          this.treeData = Data.AllNodes?Data.AllNodes.map(d=>{
+            return {
+                ...d,
+                Children:[]
+            }
+          }):[]
           this.$nextTick(() => {
             /* 新增完成后 处理树展开和选中 */
-            params && this.selectCurrentNode(params);
+            //params && this.selectCurrentNode(params);
+            if(params){
+                this.select_node = params.code;
+                this.select_id = params.id;
+            }
           });
         });
     },
@@ -471,29 +692,33 @@ export default {
       this.search_txt = "";
       this.select_node = UniqueCode;
       this.select_classify = !ChartInfoId ? ChartClassifyId : 0;
-      // if(this.select_id !== ChartInfoId) {
       this.select_id = ChartInfoId || 0;
-      // }
       this.resetNodeStyle(node);
       this.dynamicNode = node;
     },
 
     /* 添加一级目录 */
     addLevelOneHandle() {
-      this.dialog_title = this.$t('StatisticAnalysis.ChartRelevance.add_chart_classify')||"添加图表分类";
-      this.classifyForm = {
-        classify_name: "",
-      };
-      this.classifyDia = true;
+        this.classifyDiaType = 'add';
+        this.classifyForm = {
+            parent_id: 0,
+            level: 0,
+            levelVal:'',
+        };
+        this.classifyDia = true;
     },
 
     /* 编辑节点 */
     editNode(node, { ChartClassifyName,ChartClassifyNameEn, ChartClassifyId }) {
-      this.dialog_title = this.$t('StatisticAnalysis.ChartRelevance.edit_chart_classify')||"编辑图表分类";
+      this.classifyDiaType = 'edit'
+      let arr=[]
+      arr=this.getNodeParentData(node.parent,arr)
       /* 编辑目录 */
       this.classifyForm = {
-        classify_name: this.currentLang==='en'?ChartClassifyNameEn:ChartClassifyName,
-        classify_id: ChartClassifyId,
+        parentArr:arr,
+        level:node.level,
+        levelVal: this.currentLang==='en'?ChartClassifyNameEn:ChartClassifyName,
+        nodeId: ChartClassifyId,
       };
       this.classifyDia = true;
     },
@@ -520,7 +745,13 @@ export default {
           }).then(() => {
             this.delApi(ChartClassifyId, ChartInfoId);
           })
-        : null;
+        : DeleteStatus===2
+        ? this.$confirm(this.$t('Edb.MsgPrompt.del_confirm_menu_or_children'),this.$t('Confirm.prompt'),{
+            type:"warning",
+        }).then(()=>{
+            this.delApi(ChartClassifyId, ChartInfoId);
+        })
+        :null;
     },
 
     /* 删除方法 */
@@ -618,10 +849,21 @@ export default {
         })
         .then((res) => {
           if (res.Ret !== 200) return;
-
           this.chartInfo = res.Data.ChartInfo;
           this.tableData = res.Data.EdbInfoList;
+          this.defaultShowNodes = res.Data.ClassifyLevels||[];
+          this.$nextTick(()=>{
+            setTimeout(() => {
+                this.$refs.treeRef.setCurrentKey(this.select_node);
+            }, 1200);
+          })
           if(this.chartInfo.Source === 3) {
+            const SourcesFrom = this.chartInfo.SourcesFrom
+            try{
+                this.chartInfo.SourcesFrom = SourcesFrom?JSON.parse(SourcesFrom):baseSourcesFrom
+            }catch(e){
+                this.chartInfo.SourcesFrom = baseSourcesFrom
+            }
             this.relevanceChartData = {
               ChartInfo: res.Data.ChartInfo,
               EdbInfoList: res.Data.EdbInfoList,
@@ -630,12 +872,16 @@ export default {
               YDataList: [
                 {
                   Value: res.Data.YDataList[0].Value,
-                  Color: "#00f",
+                  Color: res.Data.YDataList[0].Color,
                   Name: res.Data.ChartInfo.ChartName,
                   NameEn: res.Data.ChartInfo.ChartNameEn
                 },
               ],
             };
+            //多因子
+            if(res.Data.CorrelationChartInfo.AnalysisMode===1){
+                this.relevanceChartData.YDataList = res.Data.YDataList
+            }
             this.initRelevanceChartData() 
           }else if(this.chartInfo.Source === 4) { //滚动相关性逻辑又换成曲线了
             this.relevanceChartData = {
@@ -643,7 +889,25 @@ export default {
             }
             this.setDefaultChart([res.Data.DataResp]);
           }
-          
+          this.$nextTick(()=>{
+                const _node = this.$refs.treeRef.getNode(this.select_node)
+                this.dynamicNode = _node;
+                this.dynamicNode&&this.resetNodeStyle(this.dynamicNode)
+                setTimeout(() => {
+                    let node = document.getElementById(`node${this.select_node}`)||{}
+                    let parent = document.getElementsByClassName('tree-cont')[0];
+                    //parent可视区间:[scrollTop,scrollTop+offsetHeight]
+                    //node位置:node.offsetTop
+                    const overTop = node.offsetTop+node.clientHeight+30<parent.scrollTop
+                    const overBottom = node.offsetTop+node.clientHeight+30>parent.scrollTop+parent.offsetHeight
+                    if(overTop){
+                        parent.scrollTop = node.offsetTop-60
+                    }
+                    if(overBottom){
+                        parent.scrollTop =  node.offsetTop - parent.offsetHeight/2
+                    }
+                },1500)
+            })
         });
     },
 
@@ -685,11 +949,18 @@ export default {
 
     /* 编辑图表 */
     editChartHandle() {
-      this.$router.push({
+      /* this.$router.push({
         path: "/relevancechartEditor",
         query: {
           code: this.chartInfo.UniqueCode,
         },
+      }); */
+      this.$router.push({
+        path: "/relevancechartEditorV2",
+        query: {
+          code: this.chartInfo.UniqueCode,
+          type:this.relevanceChartData.CorrelationChartInfo.AnalysisMode
+        },
       });
     },
 

+ 1089 - 0
src/views/chartRelevance_manage/relevance/relevanceChartEditorV2.vue

@@ -0,0 +1,1089 @@
+<template>
+    <div class="relevance-chart-editor-wrap" v-loading="previewMatrixLoading" element-loading-text="正在计算相关性矩阵...">
+        <div class="info-wrap content-wrap">
+            <div class="info-top">
+                <el-button type="primary" @click="previewChart">{{$t('Dialog.calculate_btn')}}</el-button>
+                <el-button type="primary" plain @click="$router.back()">{{$t('Dialog.cancel_btn')}}</el-button>
+                <!-- 操作说明 -->
+                <span @click="showExplain = true">
+                    <img style="width:15px;height:15px;" src="~@/assets/img/icons/formula-add.png" alt="">
+                    {{$t('StatisticAnalysis.ChartRelevance.opt_tip_btn')}}
+                </span>
+            </div>
+            <div class="info-form-wrap">
+                <el-form :model="infoForm" :rules="infoRules" label-width="80px" ref="infoFormRef">
+                    <!-- 分析模式 -->
+                    <el-form-item 
+                        :label="$t('StatisticAnalysis.ChartRelevance.analytical_model')" 
+                        prop="Model" required>
+                        <el-select v-model="infoForm.Model" style="width: 100%;" 
+                        :disabled="$route.query.code||isMultipleChartAdd||buttonAuth.isRelevanceChartAdd"
+                        @change="changeModel">
+                            <el-option :label="$t('StatisticAnalysis.ChartRelevance.single_indicator')" :value="1"></el-option>
+                            <el-option :label="$t('StatisticAnalysis.ChartRelevance.multiple_indicators')" :value="2"></el-option>
+                        </el-select>
+                    </el-form-item>
+                    <!-- 单因子模式需要填的 -->
+                    <singleIndForm
+                        v-if="infoForm.Model===1"
+                        :info-form="infoForm"
+                        :chart-info-data="chartInfoData"
+                        @selectTarget="handleSelectTarget"
+                        @previewChart="getPreviewSplineChart"
+                        @changeSpline="changeSplineOption"
+                    ></singleIndForm>
+                    <!-- 多因子模式需要填的 -->
+                    <multipleIndForm
+                        v-if="infoForm.Model===2"
+                        ref="multipleIndForm"
+                        :info-form="infoForm"
+                        :isMultipleChartAdd="isMultipleChartAdd"
+                        :chart-info-data="chartInfoData"
+                        :SeriesList="SeriesList"
+                        @selectTarget="handleSelectTarget"
+                        @checkRecalculate="checkRecalculate"
+                    ></multipleIndForm>
+                </el-form>
+            </div>
+        </div>
+        <div class="model-wrap">
+            <!-- 单因子模式 -->
+            <div class="single-model-wrap" v-if="infoForm.Model===1">
+                <div
+                    class="chart-min-cont"
+                    v-if="chartBatchData"
+                >
+                    <!-- 曲线图 -->
+                    <div class="card-wrapper content-wrap">
+                        <chartCard 
+                            :entryType="1"
+                            ref="chartCard1"
+                            :data="chartBatchData.CurveData"
+                            :settings="infoForm"
+                            :isChartAdd="buttonAuth.isCurveChartAdd"
+                            @saveChart="(params)=>{handleSave(params);isSaveChartToBase = true;}"
+                            @saveEdb="(params)=>{handleSave(params);isSaveEdbToBase = true;}"
+                        />
+                    </div>
+                    <!-- 相关性图表 -->
+                    <div class="card-wrapper content-wrap" v-if="chartBatchData.CorrelationData">
+                        <chartCard 
+                            :entryType="2"
+                            ref="chartCard2"
+                            :data="chartBatchData.CorrelationData"
+                            :settings="infoForm"
+                            :isChartAdd="buttonAuth.isRelevanceChartAdd"
+                            :isChartSetting="true"
+                            @chartSettingChange="handleChangeChartSetting"
+                            @saveChart="(params)=>{handleSave(params);isSaveChartToBase = true;}"
+                            @saveEdb="(params)=>{handleSave(params);isSaveEdbToBase = true;}"
+                        />
+                        <!-- 图表数据来源 -->
+                        <div class="source" v-if="chartBatchData.SourcesFrom.text&&chartBatchData.SourcesFrom.isShow">
+                            {{ $t('Edb.Detail.source') }}:{{ chartBatchData.SourcesFrom.text }}
+                        </div>
+                    </div>
+                    <!-- 滚动相关性1 -->
+                    <template v-if="chartBatchData.RollingCorrelationData">
+                        <div class="card-wrapper content-wrap" v-if="chartBatchData.RollingCorrelationData[0]">
+                            <chartCard 
+                                :entryType="3"
+                                ref="chartCard3"
+                                :data="chartBatchData.RollingCorrelationData[0]"
+                                :settings="infoForm"
+                                :isChartAdd="buttonAuth.isRollChartAdd"
+                                :isEdbAdd="buttonAuth.isRollEdbAdd"
+                                @saveChart="(params)=>{handleSave(params);isSaveChartToBase = true;}"
+                                @saveEdb="(params)=>{handleSave(params);isSaveEdbToBase = true;}"
+                            />
+                        </div>
+                        <!-- 滚动相关性2 -->
+                        <div class="card-wrapper content-wrap" v-if="chartBatchData.RollingCorrelationData[1]">
+                            <chartCard 
+                                :entryType="4"
+                                ref="chartCard4"
+                                :data="chartBatchData.RollingCorrelationData[1]"
+                                :settings="infoForm"
+                                :isChartAdd="buttonAuth.isRollChartTwoAdd"
+                                :isEdbAdd="buttonAuth.isRollEdbTwoAdd"
+                                @saveChart="(params)=>{handleSave(params);isSaveChartToBase = true;}"
+                                @saveEdb="(params)=>{handleSave(params);isSaveEdbToBase = true;}"
+                            />
+                        </div>
+                    </template>
+                </div>
+
+                <div class="nodata" v-else>
+                    <tableNoData text="暂无信息"/>
+                </div>
+            </div>
+            <!-- 多因子模式 -->
+            <div class="multiple-model-wrap" v-if="infoForm.Model===2">
+                <!-- 相关性矩阵表格 -->
+                <div class="relevant-matrix-table-box" v-if="factorTableData.length">
+                    <el-table style="width: 100%;" height="100%" :data="factorTableData" :row-class-name="getTableRowClassname" @sort-change="sortChange">
+                        <el-table-column 
+                            fixed
+                            :label="$t('StatisticAnalysis.ChartRelevance.multiple_table_head_01')"
+                            width="220px"
+                            align="center"
+                            class-name="zip-cell"
+                            >
+                            <el-table-column
+                                width="80px" align="center"
+                                class-name="zip-cell"
+                                :label="$t('Edb.Detail.e_opera')">
+                               <template slot-scope="{row}">
+                                    <template v-if="row.isSuccess">
+                                        <span style="color:#0052D9;cursor: pointer;" @click="addCurve(row)" v-if="!row.isAdd">{{$t('StatisticAnalysis.ChartRelevance.multiple_table_btn_add')}}</span>
+                                        <span style="color:#D54941;cursor: pointer;" @click="deleteCurve(row)" v-else>{{$t('StatisticAnalysis.ChartRelevance.multiple_table_btn_del')}}</span>
+                                    </template>
+                               </template>
+                            </el-table-column>
+                            <el-table-column
+                                width="140px" align="center"
+                                class-name="zip-cell"
+                                show-overflow-tooltip
+                                prop="EdbName"
+                                :label="$t('StatisticAnalysis.ChartRelevance.multiple_table_head_02')">
+                            </el-table-column>
+                        </el-table-column>
+                        <!-- 领先天数 -->
+                        <el-table-column
+                            v-if="SortedArray.length"
+                            class-name="zip-cell"
+                            :label="LeadLabelName">
+                            <el-table-column v-for="(num,index) in SortedArray" :key="num"
+                                min-width="45px"
+                                sortable="custom"
+                                prop="LeadValue"
+                                align="center"
+                                class-name="zip-cell"
+                                :label="num">
+                                <template slot-scope="{row}">
+                                    {{ row.dataList[index] }}
+                                </template>
+                            </el-table-column>
+                        </el-table-column>
+                    </el-table>
+                </div>
+                <!-- 相关性图表 -->
+                <div class="relevant-chart-box content-wrap" v-if="showMultipleChart">
+                    <chartCard
+                        ref="chartCard2"
+                        :data="multipleChartData"
+                        :settings="infoForm"
+                        :entryType="2"
+                        :isChartSetting="true"
+                        :isChartAdd="isMultipleChartAdd"
+                        height="300"
+                        @handleEdit="saveMultipleChart"
+                        @saveChart="(params)=>{handleSave(params);isSaveChartToBase = true;}"
+                        @saveEdb="(params)=>{handleSave(params);isSaveEdbToBase = true;}"
+                        @chartSettingChange="handleChangeChartSetting"
+                    />
+                    <!-- 数据来源 -->
+                    <div class="source" v-if="multipleChartData.SourcesFrom.text&&multipleChartData.SourcesFrom.isShow">
+                        {{ $t('Edb.Detail.source') }}:{{ multipleChartData.SourcesFrom.text }}
+                    </div>
+                </div>
+
+                <div class="nodata" v-if="!factorTableData.length">
+                    <tableNoData :text="$t('Common.no_info_msg')"/>
+                </div>
+            </div>
+        </div>
+        <!-- 图表保存/另存为 -->
+        <saveChartToBase
+            :isShow.sync="isSaveChartToBase"
+            :source="saveSource"
+            :saveScence="saveScence"
+            :chartData="chartData"
+            @saveBack="saveChartBack"
+            @handleSave="saveMultipleChart"
+        />
+
+        <!-- 指标保存/另存为 -->
+        <saveEdbToBase
+            :isShow.sync="isSaveEdbToBase"
+            :source="saveSource"
+            :saveScence="saveScence"
+            :chartData="chartData"
+            @saveBack="saveEdbBack"
+        />
+        <!-- 图例设置 -->
+        <saveChartSetting
+            :isSettingChartShow="isSettingChartShow"
+            :settingData="settingData"
+            @saveChartSetting="changeChartSettingBack"
+            @close="isSettingChartShow = false"
+        />
+        <!-- 操作说明 -->
+        <ExplainDialog 
+            :show-explain="showExplain"
+            @close="showExplain = false"
+        />
+    </div>
+</template>
+
+<script>
+/* api */
+import { dataBaseInterface } from '@/api/api.js'
+import * as preDictEdbInterface from '@/api/modules/predictEdbApi.js'
+import chartRelevanceApi from '@/api/modules/chartRelevanceApi'
+import {etaBaseConfigInterence} from '@/api/modules/etaBaseConfigApi.js';
+
+/*components */
+import selectTarget from '../components/selectTarget.vue'
+import singleIndForm from './components/singleIndForm.vue'
+import multipleIndForm from './components/multipleIndForm.vue'
+import chartCard from '../components/chartCard.vue'
+import saveChartToBase from '../components/saveChartTobaseDia.vue'
+import saveEdbToBase from '../components/saveEdbToBaseDia.vue'
+import saveChartSetting from './../components/saveChartSetting.vue'
+import ExplainDialog from '../components/explainDialog.vue'
+/* utils */
+import {generateSortedArray,generateXEdbValue,generateYDataValue} from './utils/index'
+import { baseForm, baseChartInfo,baseSourcesFrom } from './utils/config'
+export default {
+    components:{
+        selectTarget, singleIndForm, multipleIndForm,
+        chartCard,saveChartToBase,saveEdbToBase,saveChartSetting,
+        ExplainDialog
+    },
+    data() {
+        return {
+            typeModel:0,
+            infoForm:_.cloneDeep(baseForm),
+            chartInfoData: {},//单因子指标信息,新增时为空,编辑时有值,用于回显selectTarget,多因子取第一项
+            chartBatchData:null,//单因子图表信息
+            buttonAuth: { //单因子图表按钮控制
+                isCurveChartAdd: false,
+                isRelevanceChartAdd: false,
+                isRollChartAdd: false,
+                isRollEdbAdd: false,
+                isRollChartTwoAdd: false,
+                isRollEdbTwoAdd: false,
+            },
+            isMultipleChartAdd:false,//多因子图表按钮控制
+            SeriesList:[],//多因子系列列表,编辑时有值
+            SortedArray:[],//多因子-领先期数数组 ±N
+            factorTableData:[],//多因子-相关性矩阵表格
+            factorTableDataSortCopy:[],//复制一份,用于还原排序
+            multipleChartData:_.cloneDeep(baseChartInfo),//多因子图表信息
+            IndTarget:{},//存储标的指标信息
+            showMultipleChart:false,//是否显示多因子相关性图表
+
+            isSaveChartToBase:false,
+            isSaveEdbToBase:false,
+            saveScence:'',
+            saveSource:0,
+            chartData:'',
+
+            settingData:{SourcesFrom:{}},//图例信息
+            isSettingChartShow:false,
+
+            showExplain:false,//显示操作说明
+            SourcesFromVisable:false,//数据来源的默认值,仅在新增时生效
+            previewMatrixLoading:false,//计算相关性矩阵loading
+        };
+    },
+    computed:{
+        //领先期数label
+        LeadLabelName(){
+            const UnitEnMap = {
+                    '年': 'Year',
+                    '季': 'Season',
+                    '月': 'Month',
+                    '周': 'Week',
+                    '天': 'Day',
+                }
+            return this.$t('StatisticAnalysis.ChartRelevance.multiple_table_head_03',{unit:UnitEnMap[this.infoForm.Correlation.LeadUnit]})
+        },
+        infoRules(){
+            return {
+                EdbInfoIdA:[{
+                    required:true,
+                    message:/* '指标A未选择' */this.$t('StatisticAnalysis.ChartRelevance.infoform_rules_hint_01'),
+                    trigger:'blur'
+                }],
+                EdbInfoIdB:[{
+                    required:true,
+                    message:/* '指标B未选择' */this.$t('StatisticAnalysis.ChartRelevance.infoform_rules_hint_02'),
+                    trigger:'blur'
+                }],
+                IndTarget:[{
+                    required:true,
+                    message:/* '标的指标未选择' */this.$t('StatisticAnalysis.ChartRelevance.infoform_rules_hint_03'),
+                    trigger:'blur'
+                }],
+                'Correlation.CalculateValue':[{
+                    required:true,
+                    message:/* '计算窗口未填写' */this.$t('StatisticAnalysis.ChartRelevance.infoform_rules_hint_04'),
+                    trigger:'blur'}],
+                'Correlation.LeadValue':[{
+                    required:true,
+                    message:/* '分析周期未填写' */this.$t('StatisticAnalysis.ChartRelevance.infoform_rules_hint_05'),
+                    trigger:'blur'
+                }]
+            }
+        },
+        chartInfo(){ //单因子系列保存/更新/另存为会用到
+            return this.infoForm
+        }
+    },
+    mounted(){
+        this.getRelevanceChartDetail()
+        this.getChartBaseSetting()
+    },
+    methods: {
+        //获取图表全局设置
+        async getChartBaseSetting(){
+            //目前是用基本配置的接口,后续有多个配置再改
+            const res = await etaBaseConfigInterence.getBaseConfig()
+            if(res.Ret!==200) return 
+            const {ChartSourceDisplay} = res.Data||{}
+            this.SourcesFromVisable = ChartSourceDisplay==='true'?true:false
+            //多因子的配置一开始就初始化,在这里设置默认值
+            if(!this.$route.query.code){
+                this.multipleChartData.SourcesFrom.isShow = this.SourcesFromVisable
+            }
+        },
+        sortChange({column,prop,order}){
+            const {label} = column
+            this.factorTableData.sort((a,b)=>{
+                if(order==='ascending'){
+                    return a.dataList[label] - b.dataList[label]
+                }else{
+                    return b.dataList[label] - a.dataList[label]
+                }
+            })
+            //取消order 排序还原为factorTableDataSortCopy的顺序
+            if(!order){
+                const table = this.factorTableDataSortCopy.map(copyItem=>{
+                    return this.factorTableData.find(i=>i.EdbInfoId===copyItem.EdbInfoId&&i.SeriesId===copyItem.SeriesId)
+                })
+                this.factorTableData = table
+            }
+            
+
+        },
+        //禁用表格行的样式
+        getTableRowClassname({row}){
+            if(!row.isSuccess){
+                return 'disable-row'
+            }
+            return ''
+        },
+        changeModel(){
+            this.typeModel = this.infoForm.Model
+            this.resetForm()
+        },
+        //重置表单
+        resetForm(){
+            this.infoForm = _.cloneDeep(baseForm)
+            this.chartBatchData = null
+            this.factorTableData = []
+            this.factorTableDataSortCopy=[]
+            this.showMultipleChart = false
+            this.infoForm.Model = this.typeModel
+            this.typeModel = 0
+        },
+        //选择指标后获取指标详情
+        async getEdbDetail({EdbInfoId,EdbInfoType}) {
+            const { Data } = EdbInfoType 
+            ? await preDictEdbInterface.edbDetail({EdbInfoId})
+            : await dataBaseInterface.calculateDetail({EdbInfoId})
+            return { 
+                max: EdbInfoType ? Data.MaxValue : Data.EdbInfoDetail.MaxValue,
+                min: EdbInfoType ? Data.MinValue : Data.EdbInfoDetail.MinValue,
+                data:EdbInfoType ? Data : Data.EdbInfoDetail,
+            }
+        },
+        //选择指标
+        async handleSelectTarget({type,target}){
+            if(!target) return
+            this.infoForm[type] = target.EdbInfoId||''
+            const {max,min,data} = await this.getEdbDetail(target)
+            if(type==='IndTarget'){
+                this.IndTarget = data
+                //点计算时,才真正赋值ChartInfo
+                //this.multipleChartData.ChartInfo = data
+            }
+            //若为单因子,选择AB指标后预览曲线图
+            if(!['EdbInfoIdA','EdbInfoIdB'].includes(type)) return 
+            if(type==='EdbInfoIdA'){
+                this.infoForm.Curve.LeftMin = min;
+                this.infoForm.Curve.LeftMax = max;
+            }else if(type==='EdbInfoIdB'){
+                this.infoForm.Curve.RightMin = min;
+                this.infoForm.Curve.RightMax = max;
+            }
+            this.getPreviewSplineChart()
+        },
+        //单因子-选择指标后生成曲线图
+        async getPreviewSplineChart(){
+            if(!this.infoForm.EdbInfoIdA || !this.infoForm.EdbInfoIdB) return
+            if(this.infoForm.Curve.DateType===5&&!this.infoForm.Curve.Date[0]) return 
+            let params = {
+                ...this.infoForm 
+            }
+            const res = await chartRelevanceApi.previewSplineChart(params);
+            if(res.Ret !== 200) return
+            this.chartBatchData = this.chartBatchData?{
+                ...this.chartBatchData,
+                CurveData: res.Data.CurveData
+            }:{CurveData: res.Data.CurveData};
+        },
+        //单因子-曲线配置变化时重绘
+        changeSplineOption() {
+            const { LeftMin,LeftMax,RightMin,RightMax,IsOrder } = this.infoForm.Curve;
+            this.$refs.chartCard1.options.yAxis[0].max = Number(LeftMax);
+            this.$refs.chartCard1.options.yAxis[0].min = Number(LeftMin);
+
+            this.$refs.chartCard1.options.yAxis[1].max = Number(RightMax);
+            this.$refs.chartCard1.options.yAxis[1].min = Number(RightMin);
+            this.chartBatchData.CurveData.EdbInfoList[1].IsOrder = IsOrder;
+        },
+        //预览相关性图表/相关性矩阵
+        async previewChart(){
+            //表单校验
+            await this.$refs.infoFormRef.validate()
+            //相关性值校验
+            if(!this.checkValue()) return 
+            
+            if(this.infoForm.Model===1){
+                //单因子:渲染相关性图表,需要保存MultipleGraphConfigId
+                const res = await chartRelevanceApi.chartOptionsSet({...this.infoForm});
+                if(res.Ret !== 200) return
+                const { MultipleGraphConfigId } = res.Data;
+                this.infoForm.MultipleGraphConfigId = MultipleGraphConfigId;
+                this.previewSingleChart({})
+            }else{
+                //已添加指标后重新计算
+                if(this.isMultipleChartAdd){
+                    const {ChartInfoId,UniqueCode,ClassifyId} = this.multipleChartData.ChartInfo
+                    this.multipleChartData.ChartInfo = this.IndTarget
+                    this.multipleChartData.ChartInfo.ChartInfoId = ChartInfoId
+                    this.multipleChartData.ChartInfo.UniqueCode = UniqueCode
+                    this.multipleChartData.ChartInfo.ClassifyId = ClassifyId
+                }else{
+                    this.multipleChartData.ChartInfo = this.IndTarget
+                }
+                //多因子:根据分析周期生成相关性矩阵,在未添加曲线前不生成图表
+                this.previewMultipleTable()
+            }
+        },
+        //校验规则
+        checkValue() {
+            //只用于校验规则条件大小
+            let checkBool = true;
+            const valueMap = {
+                '年': 365,
+                '季': 90,
+                '月': 30,
+                '周': 7,
+                '天': 1
+            }
+            const { Correlation } = this.infoForm;
+            if(Correlation.CalculateValue*valueMap[Correlation.CalculateUnit] < Correlation.LeadValue*valueMap[Correlation.LeadUnit]*2) {
+                this.infoForm.Correlation.CalculateValue = 0;
+                this.$message.warning(/* '相关性计算窗口必须≥2*分析周期' */this.$t('StatisticAnalysis.ChartRelevance.check_value_hint'))
+                checkBool = false
+            }
+            return checkBool
+        },
+        //预览单因子图表 初始化 chartBatchData
+        async previewSingleChart(initConfig=null){
+            const res = await chartRelevanceApi.previewChartBatch({...this.infoForm});
+            if(res.Ret !== 200) return
+            this.chartBatchData = res.Data;
+            //第一次初始化时,需初始化图例和数据来源信息
+            if(initConfig){
+                const {CorrelationExtraConfig,SourcesFrom} = initConfig
+                try{
+                    this.chartBatchData.SourcesFrom = SourcesFrom?JSON.parse(SourcesFrom):_.cloneDeep(baseSourcesFrom)
+                }catch(e){
+                    this.chartBatchData.SourcesFrom = _.cloneDeep(baseSourcesFrom)
+                }
+                //新增时 来源与基本配置设置的一致
+                if(!this.$route.query.code){
+                    this.chartBatchData.SourcesFrom.isShow = this.SourcesFromVisable
+                }
+                //拼接来源
+                const {EdbInfoList} = res.Data.CorrelationData
+                const tempStr = EdbInfoList[0].SourceName+','+EdbInfoList[1].SourceName
+                this.chartBatchData.SourcesFrom.text += `${this.chartBatchData.SourcesFrom.text.length?',':''}${tempStr}`
+                //拼接后去重
+                let concatSourceArr = `${this.chartBatchData.SourcesFrom.text}`.split(',');
+                let sourceStr = Array.from(new Set(concatSourceArr)).join(',');
+                this.chartBatchData.SourcesFrom.text = sourceStr
+                try{
+                    const {LegendConfig} = CorrelationExtraConfig?JSON.parse(CorrelationExtraConfig):{LegendConfig:[]}
+                    this.chartBatchData.CorrelationData.YDataList[0].Color = LegendConfig[0].Color
+                    this.chartBatchData.CorrelationData.YDataList[0].Name = LegendConfig[0].Legendlame
+                }catch(e){
+
+                }
+            }
+        },
+        //预览多因子矩阵 初始化 factorTableData
+        previewMultipleTable(){
+            //全局loading
+            this.previewMatrixLoading = true
+            const {IndTarget,Correlation} = this.infoForm
+            const SeriesIds = this.$refs.multipleIndForm.factorList.map(i=>i.SeriesId)
+            //清空表格和图表
+            this.factorTableData = []
+            this.factorTableDataSortCopy=[]
+            this.multipleChartData.YDataList=[]
+            this.showMultipleChart = false
+            chartRelevanceApi.getCorrelationMatrix({
+                BaseEdbInfoId:IndTarget,
+                Correlation,
+                SeriesIds,
+            }).then(res=>{
+                //loading结束
+                this.previewMatrixLoading = false
+                if(res.Ret!==200) return
+
+                const {Fail=[],Success=[]} = res.Data
+                if(!Success||Success&&!Success.length){
+                    this.$message.warning('所有指标计算失败,请重新选择指标')
+                    return 
+                }
+                //标记失败的
+                const FailList = Fail?Fail.map(i=>{
+                    return {
+                        ...i,
+                        isSuccess:false,
+                        dataList:[]
+                    }
+                }):[]
+                const SuccessList = Success.map(i=>{
+                    return {
+                        ...i,
+                        isSuccess:true,
+                        isAdd:false,
+                        dataList:i.Values.map(v=>v.YData)
+                    }
+                })
+                this.factorTableData = [...SuccessList,...FailList]
+                this.factorTableDataSortCopy = _.cloneDeep(this.factorTableData)
+                //获取分析周期,生成±分析周期的数组
+                const {LeadValue,LeadUnit,CalculateUnit,CalculateValue} = this.infoForm.Correlation
+                this.SortedArray = generateSortedArray(LeadValue)
+                //设置图表默认值
+                this.multipleChartData.ChartInfo.Source = 3
+                this.multipleChartData.ChartInfo.ChartName = this.multipleChartData.ChartInfo.EdbName+`相关性分析(${CalculateValue}${CalculateUnit})`
+                this.multipleChartData.CorrelationChartInfo = {
+                    LeadValue:LeadValue,
+                    LeadUnit:LeadUnit
+                }
+                this.multipleChartData.XEdbIdValue = generateXEdbValue(LeadValue)
+            })
+        },
+        checkRecalculate(){
+            //若当前已预览过图表/矩阵,则重新请求矩阵接口,初始化矩阵和图表数据
+            if(this.showMultipleChart||this.factorTableData.length){
+                this.previewMultipleTable()
+            }
+        },
+        //相关性矩阵-添加曲线
+        addCurve(row){
+            const length = this.multipleChartData.YDataList.length
+            if(length>=10) return this.$message.warning('最多只支持添加10条曲线')
+            this.showMultipleChart = true
+            
+            const colors = ['#00f','#f00','#999','#000','#7cb5ec', '#90ed7d', '#f7a35c', '#8085e9', '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1']
+            this.multipleChartData.YDataList.push({
+                Id:row.EdbInfoId,
+                SeriesId:row.SeriesId,
+                Value:generateYDataValue(row.dataList,this.infoForm.Correlation.LeadValue),
+                Color:colors[length%colors.length],
+                Name:row.EdbName,
+                NameEn:row.EdbNameEn
+            })
+            row.isAdd = true
+            //数据来源拼接
+            this.multipleChartData.SourcesFrom.text += `${this.multipleChartData.SourcesFrom.text.length?',':''}${row.SourceName}`
+            //拼接后去重
+            let concatSourceArr = `${this.multipleChartData.SourcesFrom.text}`.split(',');
+            let sourceStr = Array.from(new Set(concatSourceArr)).join(',');
+            this.multipleChartData.SourcesFrom.text = sourceStr
+        },
+        //相关性矩阵-删除曲线
+        deleteCurve(row){
+            const index = this.multipleChartData.YDataList.findIndex(i=>i.Id===row.EdbInfoId&&i.SeriesId===row.SeriesId)
+            index!==-1&&this.multipleChartData.YDataList.splice(index,1)
+            row.isAdd = false
+            if(!this.multipleChartData.YDataList.length){
+                this.showMultipleChart = false
+            }
+        },
+        //打开图例设置弹窗
+        handleChangeChartSetting(data){
+            //单多因子的data结构是相同的
+            /**
+             * 图表名称:data.ChartName/en
+             * 图例:data.YDataList.Color data.YDataList.Name
+             * 数据来源:新字段 SourcesFrom.text
+             * 数据来源开关:新字段 SourcesFrom.isShow
+             */
+            const SourcesFrom = this.infoForm.Model===1?_.cloneDeep(this.chartBatchData.SourcesFrom):_.cloneDeep(this.multipleChartData.SourcesFrom)
+            this.settingData = {
+                SourcesFrom,
+                chartName:data.ChartName,
+                YDataList:_.cloneDeep(data.YDataList)
+            }
+            this.isSettingChartShow = true
+        },
+        //图例设置回调
+        changeChartSettingBack(){
+            const {chartName,YDataList,SourcesFrom} = this.settingData
+            if(this.infoForm.Model===1){
+                this.chartBatchData.CorrelationData.ChartInfo.ChartName = chartName
+                this.chartBatchData.CorrelationData.YDataList = YDataList
+                this.chartBatchData.SourcesFrom = SourcesFrom
+            }else{
+                //多因子
+                this.multipleChartData.ChartInfo.ChartName = chartName
+                this.multipleChartData.YDataList = YDataList
+                this.multipleChartData.SourcesFrom = SourcesFrom
+            }
+            this.isSettingChartShow = false
+        },
+        //打开保存/另存为图表/指标弹窗
+        handleSave({type,chartData,scence}){
+            this.saveSource = type
+            this.chartData = chartData;
+            this.saveScence = scence;
+           /*  this.isSaveEdbToBase = true;
+            this.isSaveChartToBase = true; */
+        },
+        //单因子-保存/另存为指标回调
+        saveEdbBack({source}){
+            if(source===3) this.buttonAuth.isRollEdbAdd = true;
+            else if(source===4)  this.buttonAuth.isRollEdbTwoAdd = true;
+        },
+        //单因子-保存/另存为图表回调
+        saveChartBack({source,id}){
+            this.$message.success(this.$t('MsgPrompt.saved_msg'));
+            this.setButtonAuth(source);
+            //设置封面图
+            this.$refs[`chartCard`+source].setChartImage(source,id)
+        },
+        //单因子-保存图表后按钮控制 
+        setButtonAuth(source) {
+            const sourceMap = {
+                1: 'isCurveChartAdd',
+                2: 'isRelevanceChartAdd',
+                3: 'isRollChartAdd',
+                4: 'isRollChartTwoAdd'
+            }
+            this.buttonAuth[sourceMap[source]] = true;
+        },
+        //编辑-获取相关性图表详情
+        getRelevanceChartDetail(){
+            //判断是单因子or多因子
+            if(this.$route.query.type!=1){
+                this.getSingleDetail()
+            }else{
+                this.getMultiplyDetail()
+            }
+            
+        },
+        //获取单因子图表详情
+        async getSingleDetail(){
+            if(!this.$route.query.code) return
+            const res = await chartRelevanceApi.getOptionByCode({ UniqueCode: this.$route.query.code});
+            if(res.Ret !== 200) return 
+            this.chartInfoData = {
+                EdbInfoList: res.Data.EdbInfoList
+            }
+            //初始化infoForm
+            const { MultipleGraphConfigId,EdbInfoIdA,EdbInfoIdB,Curve,Correlation,RollingCorrelation } = res.Data.MultipleGraphConfig;
+            this.infoForm = {
+                Model:1,
+                MultipleGraphConfigId,
+                EdbInfoIdA,
+                EdbInfoIdB,
+                Curve: {
+                ...JSON.parse(Curve),
+                Date: [JSON.parse(Curve).StartDate,JSON.parse(Curve).EndDate],
+                },
+                Correlation: JSON.parse(Correlation),
+                RollingCorrelation: JSON.parse(RollingCorrelation)
+            }
+            //初始化权限
+            this.buttonAuth =  {
+                isCurveChartAdd: res.Data.ChartMappingList.some(_ => _.MultipleLocationSource===1),
+                isRelevanceChartAdd: res.Data.ChartMappingList.some(_ => _.MultipleLocationSource===2),
+                isRollChartAdd: res.Data.ChartMappingList.some(_ => _.MultipleLocationSource===3),
+                isRollEdbAdd: res.Data.EdbMappingList.some(_ => _.MultipleLocationSource===3),
+                isRollChartTwoAdd: res.Data.ChartMappingList.some(_ => _.MultipleLocationSource===4),
+                isRollEdbTwoAdd: res.Data.EdbMappingList.some(_ => _.MultipleLocationSource===4)
+            }
+            //初始化chartBatch
+            const RelevanceInfo = res.Data.ChartMappingList.find(_=>_.MultipleLocationSource===2)||{}
+            const {CorrelationExtraConfig,SourcesFrom} = RelevanceInfo
+            this.previewSingleChart({CorrelationExtraConfig,SourcesFrom})
+        },
+        //获取多因子图表详情
+        getMultiplyDetail(){
+            if(!this.$route.query.code) return
+            //获取图表详情
+            chartRelevanceApi.getMultipleChartDetail({
+                UniqueCode:this.$route.query.code
+            }).then(res=>{
+                if(res.Ret!==200) return
+                const {ChartName,ChartClassifyId,ChartInfoId,UniqueCode,ExtraConfig,SourcesFrom} = res.Data.ChartInfo
+                this.multipleChartData.ChartInfo = {
+                    ClassifyId:ChartClassifyId,ChartName,ChartInfoId,UniqueCode,Source:3
+                }
+                const {EdbInfoList,XEdbIdValue,YDataList,CorrelationChartInfo} = res.Data
+                const {LeadValue,LeadUnit} = CorrelationChartInfo
+                this.multipleChartData.CorrelationChartInfo = {
+                    LeadValue,LeadUnit
+                }
+                this.multipleChartData.XEdbIdValue = XEdbIdValue
+                this.multipleChartData.EdbInfoList = EdbInfoList
+                this.multipleChartData.YDataList = YDataList.map(i=>{
+                    return {
+                        ...i,
+                        Id:i.SeriesEdb.EdbInfoId,
+                        SeriesId:i.SeriesEdb.SeriesId
+                    }
+                })
+                try{
+                    this.multipleChartData.SourcesFrom = SourcesFrom?JSON.parse(SourcesFrom):_.cloneDeep(baseSourcesFrom)
+                }catch(e){
+                    this.multipleChartData.SourcesFrom = _.cloneDeep(baseSourcesFrom)
+                }
+                this.showMultipleChart = true
+                this.isMultipleChartAdd = true
+
+            })
+            //获取表单及矩阵详情
+            chartRelevanceApi.getMultipleFactorDetail({
+                UniqueCode:this.$route.query.code
+            }).then(res=>{
+                if(res.Ret!==200) return 
+                const {BaseEdbInfo,CorrelationConfig,EdbSeries,CorrelationMatrix} = res.Data
+                const {LeadValue,LeadUnit,CalculateValue,CalculateUnit} = CorrelationConfig
+                this.infoForm.Model = 2
+                //基础信息表单
+                this.infoForm = {
+                    Model:2,
+                    IndTarget:BaseEdbInfo.EdbInfoId,
+                    Correlation:{
+                        LeadValue,LeadUnit,CalculateValue,CalculateUnit
+                    }
+                }
+                //标的因子信息
+                this.IndTarget = _.cloneDeep(BaseEdbInfo)
+                this.chartInfoData = {
+                    EdbInfoList:[BaseEdbInfo]
+                }
+                //因子列表
+                this.SeriesList = EdbSeries
+                //相关性矩阵
+                this.factorTableData = CorrelationMatrix.map(i=>{
+                    return {
+                        ...i,
+                        isSuccess:true,
+                        isAdd:i.Used,
+                        dataList:i.Values.map(v=>v.YData)
+                    }
+                })
+                this.factorTableDataSortCopy = _.cloneDeep(this.factorTableData)
+                this.SortedArray = generateSortedArray(LeadValue)
+            })
+        },
+        //多因子-保存/更新/另存为图表
+        async saveMultipleChart({ChartName,ClassifyId,type}){
+            const {IndTarget,Correlation} = this.infoForm
+            const {SourcesFrom,YDataList} = this.multipleChartData
+            const SeriesEdb = YDataList.map(i=>{
+                return {
+                    SeriesId:i.SeriesId,
+                    EdbInfoId:i.Id
+                }
+            })
+            //BaseEdbInfoId优先取当前图表内的EdbInfoId,若没有值取表单内的
+            const BaseEdbInfoId = this.multipleChartData.ChartInfo.EdbInfoId||IndTarget
+            const SeriesIds = this.$refs.multipleIndForm.factorList.map(i=>i.SeriesId)
+            const LegendConfig = YDataList.map(i=>{
+                return {
+                    LegendName:i.Name,
+                    SeriesId:i.SeriesId,
+                    EdbInfoId:i.Id,
+                    Color:i.Color
+                }
+            })
+            let params = {
+                ChartName,ClassifyId,
+                AnalysisMode:1,
+                BaseEdbInfoId:BaseEdbInfoId/* IndTarget */,
+                FactorCorrelation:{
+                    ...Correlation,
+                    SeriesEdb,
+                    SeriesIds
+                },
+                ExtraConfig:{
+                    LegendConfig,
+                },
+                SourcesFrom,
+            }
+            const {ChartInfoId} = this.multipleChartData.ChartInfo
+            const isEdit = ChartInfoId&&type!=='saveOther'
+            //另存为/新增->add 更新->edit
+            const res = isEdit
+                ?await chartRelevanceApi.editMultipleFactor({...params,ChartInfoId})
+                :await chartRelevanceApi.addMultipleFactor({...params,SaveAs:type==='saveOther'})
+            //计算中...
+            if(res.Ret!==200) return 
+            if(!isEdit){
+                this.isMultipleChartAdd = true
+                this.multipleChartData.ChartInfo.ChartInfoId = res.Data.ChartInfoId
+                this.multipleChartData.ChartInfo.UniqueCode = res.Data.UniqueCode
+                this.multipleChartData.ChartInfo.ClassifyId = res.Data.ClassifyId
+            }
+            //更新图表名称
+            this.multipleChartData.ChartInfo.ChartName = ChartName
+            this.$message.success(`${isEdit?'更新':'保存'}成功`)
+            //设置缩略图
+            this.$refs[`chartCard`+2].setChartImage(2,res.Data.ChartInfoId)
+            this.isSaveChartToBase = false
+        },
+    },
+};
+</script>
+
+<style lang="scss">
+.relevance-chart-editor-wrap{
+    display: flex;
+    *{
+        box-sizing: border-box;
+    }
+    .content-wrap{
+        background: #fff;
+        border: 1px solid #ececec;
+        border-radius: 4px;
+        .source{
+            padding-left: 20px;
+            padding-bottom:20px;
+            margin-top: -20px;
+        }
+    }
+    .info-wrap{
+        width:380px;
+        min-width: 380px;
+        margin-right: 20px;
+        display: flex;
+        flex-direction: column;
+        height: calc(100vh - 120px);
+        .info-top{
+            padding: 15px 20px;
+            border-bottom: 1px solid #ececec;
+            box-shadow: 0px 3px 6px rgba(167, 167, 167, 0.09);
+            display: flex;
+            align-items: center;
+            >span{
+                color:#0052D9;
+                cursor: pointer;
+                flex: 1;
+                text-align: right;
+                img{
+                    vertical-align: middle;
+                }
+            }
+        }
+        .info-form-wrap{
+            flex: 1;
+            overflow-y: auto;
+            padding:20px;
+            .el-form-item{
+                margin-bottom: 10px;
+            }
+            .form-box{
+                margin-top: 10px;
+                padding-top: 10px;
+                border-top:1px dashed #DCDFE6;
+            }
+            .model-form{
+                .select-target{
+                    display: flex;
+                    margin-top: 10px;
+                    .el-form-item__label{
+                        flex-shrink: 0;
+                    }
+                    .el-form-item__content{
+                        margin-left: 0 !important;
+                        .el-select{
+                            margin-top: 10px !important;
+                        }
+                    }
+                    .el-date-editor.el-input, .el-date-editor.el-input__inner{
+                        width: auto;
+                    }
+                }
+                .flex-form-item{
+                    .el-form-item__content{
+                        display: flex;
+                        gap:0 5px;
+                    }
+                }
+            }
+            .multiple-model-form{
+                .factor-form-item{
+                    .el-form-item {
+                        margin-bottom: 0;
+                    }
+                    .el-form-item__label{
+                        width:auto !important;
+                    }
+                    .factor-list{
+                        .list-item{
+                            padding:10px 20px;
+                            background-color: #EBEFF6;
+                            border:1px solid #C8CDD9;
+                            cursor: pointer;
+                            display: flex;
+                            justify-content: space-between;
+                            align-items: center;
+                            border-bottom: none;
+                            &:last-child{
+                                border-bottom: 1px solid #C8CDD9;
+                            }
+                        }
+                    }
+                    .add-factor-btn{
+                        margin-top: 20px;
+                        display: flex;
+                        gap:10px;
+                        align-items: center;
+                        cursor: pointer;
+                        color:#0052D9;
+                        img{
+                            width: 15px;
+                            height: 15px;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    .model-wrap{
+        flex:1;
+        height: calc(100vh - 120px);
+        overflow-x: hidden;
+        .single-model-wrap{
+            width: 100%;
+            overflow: hidden;
+        }
+        .chart-min-cont {
+            height: calc(100vh - 120px);
+            overflow-y: auto;
+            display: flex;
+            flex-wrap: wrap;
+            gap:15px;
+            .card-wrapper {
+                width: 48%;
+                min-height: 350px;
+                min-width:410px;
+                .card-item {
+                    padding: 20px;
+                    .top {
+                        display: flex;
+                        justify-content: space-between;
+                        align-items: center;
+                        margin-bottom: 15px;
+                    }
+                    .title {
+                        font-size: 15px;
+                        text-align: left;
+                        margin: 10px 0;
+                    }
+                }
+            }
+        }
+        .nodata {
+            height: calc(100vh - 120px);
+            background-color: #fff;
+            text-align: center;
+            font-size: 16px;
+            color: #666;
+            padding: 100px 0;
+        }
+        .multiple-model-wrap{
+            display: flex;
+            flex-direction: column;
+            justify-content: space-between;
+            height: calc(100vh - 120px);
+            .relevant-matrix-table-box,.relevant-chart-box{
+                height: 49%;
+                overflow: auto;
+            }
+            
+            .relevant-matrix-table-box{
+                display: flex;
+                .el-table{
+                    flex:1;
+                    thead{
+                        color:#333;
+                    }
+                    th.zip-cell{
+                        padding:0;
+                        font-size: 12px;
+                        background: #F5F7FA !important;
+                        .cell{
+                            padding:0;
+                            .caret-wrapper{
+                                height:24px;
+                                width:20px;
+                                .sort-caret.ascending{
+                                    top:0;
+                                }
+                                .sort-caret.descending{
+                                    bottom:0;
+                                }
+                            }
+                        }
+                    }
+                    td.zip-cell{
+                        padding:0;
+                        font-size: 12px;
+                        background-color: #fff !important;
+                        .cell{
+                            padding:0;
+                            line-height: 23px;//调整单元格高度
+                        }
+                    }
+                }
+            }
+            .relevant-chart-box{
+                .card-item {
+                    padding: 20px;
+                    .top {
+                        display: flex;
+                        justify-content: space-between;
+                        align-items: center;
+                        .title {
+                            font-size: 15px;
+                            text-align: left;
+                            margin: 10px 0;
+                        }
+                    }
+                    
+                }
+            }
+        }
+    }
+    .disable-row{
+        pointer-events: none;
+        opacity: 0.5;
+        cursor: default; 
+    }
+}
+</style>

+ 111 - 0
src/views/chartRelevance_manage/relevance/utils/config.js

@@ -0,0 +1,111 @@
+//相关性表单
+export const baseForm = {
+    Model:1,//模式 1单因子 2 多因子
+    EdbInfoIdA:'',//指标A
+    EdbInfoIdB:'',//指标B
+    IndTarget:'',//标的指标
+    Curve:{//曲线图
+        DateType:3,
+        Date:'',
+        StartDate:'',
+        EndDate:'',
+        LeftMin:'',
+        LeftMax:'',
+        RightMin:'',
+        RightMax:'',
+        EdbInfoType:true,//显示为标准指标(true)或领先指标(false)
+        LeadValue:0,
+        LeadUnit:'天',
+    },
+    Correlation:{//相关性
+        CalculateValue:0,//计算窗口
+        CalculateUnit:'天',//计算窗口单位(时间)
+        LeadValue:0,//分析周期
+        LeadUnit:'天',//分析周期单位(时间)
+    },
+    RollingCorrelation:[//滚动相关性
+        {
+            CalculateValue:0,
+            CalculateUnit:'天',
+            LeadValue:0,
+            LeadUnit:'天'
+        },
+        {
+            CalculateValue:0,
+            CalculateUnit:'天',
+            LeadValue:0,
+            LeadUnit:'天'
+        }
+    ],
+}
+//多因子图表信息
+export const baseChartInfo = {
+    ChartInfo:{
+        Source:3,
+        ChartName:'',//与单因子保持一致,用于保存/另存为回显
+        ChartInfoId:'',//新增前为空
+        UniqueCode:'',//新增前为空
+        ClassifyId:'',//新增前为空
+        /**
+         * EdbInfoId 当前页面点击计算后有值 标的指标的ID
+         * EdbName 当前页面点击计算后有值 标的指标的名称
+         */
+    },
+    EdbInfoList:[],//第一项为标的指标,其他项为添加的指标
+    XEdbIdValue:[],//SourtedArray
+    CorrelationChartInfo:{
+        LeadValue:0,//分析周期
+        LeadUnit:'天',//分析周期单位(时间)
+    },
+    YDataList:[
+        /* {
+            Value:[],//factorTableData.dataList
+            Color:'#00f',
+            Name:'',//factorTableData.name
+            NameEn:''
+        } */
+    ],
+    SourcesFrom:{
+        isShow:true,//是否显示
+        text:'',//内容
+        color:'#333',
+        fontSize:12,
+    },//
+}
+//多因子-相关性矩阵表格
+const factorTableData = [
+    {
+        EdbInfoId:'指标ID',
+        SeriesId:'指标所属系列ID',//与EdbInfoId一起表示指标的唯一性
+        EdbName:'指标名称',
+        dataList:[],//领先期数数据,下标需和期数对应
+        isAdd:false,//是否已添加曲线
+    }
+]
+//多因子-因子系列
+const factorList = [
+    {
+        "SeriesId": 4, //系列Id
+        "SeriesName": "系列名称A001", //系列名称
+        "EdbInfoType": 0, //0指标,1预测指标
+        "CalculateStep":[ //计算公式
+            {
+                "Formula": "", //N值/移动天数/指数修匀alpha值/计算公式等
+                "Calendar": "", //公历/农历
+                "Source": 3, //计算方式来源
+                "Sort": 1 
+            },
+        ],
+        "EdbMappings":[
+            
+        ],//所选的指标
+    },
+]
+
+//数据来源
+export const baseSourcesFrom = {
+    isShow:true,//是否显示
+    text:'',//内容
+    color:'#333',
+    fontSize:12,
+}

+ 70 - 0
src/views/chartRelevance_manage/relevance/utils/index.js

@@ -0,0 +1,70 @@
+//相关性矩阵表头
+export const generateSortedArray = (N)=>{
+    let positiveArr = [];
+    for (let i = 0; i <= N; i++) {
+        positiveArr.push(i);
+    }
+    
+    let negativeArr = [];
+    for (let i = -1; i >= -N; i--) {
+        negativeArr.push(i);
+    }
+    
+    return positiveArr.concat(negativeArr);
+}
+//mock用 相关性图表x轴
+export const generateXEdbValue = (N)=>{
+    let positiveArr = [];
+    for (let i = 0; i <= N; i++) {
+        positiveArr.push(i);
+    }
+    
+    let negativeArr = [];
+    for (let i = -N; i <= -1; i++) {
+        negativeArr.push(i);
+    }
+    return [...negativeArr,...positiveArr]
+}
+//转换YDataList data是领先期数从0开始的数组
+export const generateYDataValue = (data,n)=>{
+    //取[n+1,length]项 为-1~-n 翻转为-n~-1
+    let arr = data.slice(n+1).reverse()
+    //data从下标n+1处放置
+    let dataArr = data.slice(0,n+1)
+    return [...arr,...dataArr]
+}
+//判断两个指标ID数组是否有变化:指标ID一致视为无变化
+export const checkListChange = (listA,listB)=>{
+    // 将两个数组转换为 Set 对象
+    const setA = new Set(listA);
+    const setB = new Set(listB);
+
+    if (setA.size !== setB.size) {
+        return true;
+    }
+    // 检查 setA 中的每一项是否都在 setB 中
+    for (const item of setA) {
+        if (!setB.has(item)) {
+            return true;
+        }
+    }
+    // 如果所有检查都通过,说明两个数组包含相同的项
+    return false;
+}
+//判断两个计算方式是否有变化:顺序,Source以及Formula的值一致视为无变化
+export const checkStepsChange = (stepsA,stepsB)=>{
+    if(stepsA.length!==stepsB.length) return true
+
+     // 逐个比较数组中的每一项是否都相同且顺序一致
+     for (let i = 0; i < stepsA.length; i++) {
+        if (  stepsA[i].Sort!==stepsB[i].Sort
+            ||stepsA[i].Source!==stepsB[i].Source
+            ||stepsA[i].Formula!==stepsB[i].Formula) {
+            return true
+        }
+        if(stepsA[i].Source===11&&stepsA[i].Calendar!==stepsB[i].Calendar){
+            return true
+        }
+    }
+    return false
+}

+ 17 - 4
src/views/dataEntry_manage/components/SaveChartOther.vue

@@ -33,7 +33,8 @@
             label: currentLang==='en'?'ChartClassifyNameEn':'ChartClassifyName',
             value: 'ChartClassifyId',
             children: 'Children',
-            emitPath: false
+            emitPath: false,
+            checkStrictly:isRelevanceChart
           }"
           style="width: 80%"
           :placeholder="$t('Chart.InputHolderAll.input_classify')"
@@ -75,6 +76,11 @@ export default {
       }
     }
   },
+  computed:{
+    isRelevanceChart(){
+        return ['/chartrelevance'].includes(this.$route.path)||this.source===3||this.source===4
+    }
+  },
   data() {
     return {
       form: {
@@ -109,7 +115,7 @@ export default {
       if([2,5,'good_price'].includes(this.source)){//商品价格
         res=await futuresInterface.classifyList()
       }else if([3,4,'relevance_chart'].includes(this.source)){//相关性图表
-        res=await chartRelevanceApi.classifyList()
+        res=await chartRelevanceApi.classifyTree()
       }else if([6,'fitting_equation'].includes(this.source)){//拟合方程
         res=await fittingEquationInterface.classifyList()
       }else if([7,8,9,'statistic_feature'].includes(this.source)) { //标准差 百分位 频率图
@@ -125,7 +131,7 @@ export default {
       if([2,5,'good_price'].includes(this.source)){//商品价格
         this.filterNodes(res.Data.AllNodes,1)
       }else if([3,4,'relevance_chart'].includes(this.source)){//相关性图表
-        this.filterNodes(res.Data.AllNodes,1)
+        this.filterNodesAll(res.Data.AllNodes)
       }else if([6,'fitting_equation'].includes(this.source)){//拟合方程
         this.filterNodes(res.Data.AllNodes,1)
       }else if([7,8,9,'statistic_feature'].includes(this.source)) { //标准差 百分位 频率图
@@ -139,7 +145,14 @@ export default {
 			this.classifyOptions = res.Data.AllNodes || [];
     
 		},
-
+		filterNodesAll(arr){
+			arr.length && arr.forEach(item => {
+				item.Children && item.Children.length && this.filterNodesAll(item.Children)
+				if(!item.Children.length) {
+					delete item.Children
+				}
+			})
+		},
 		// 递归改变第三级目录结构
 		filterNodes(arr,n) {
 			arr.length && arr.forEach(item => {

+ 2 - 2
src/views/dataEntry_manage/mixins/chartPublic.js

@@ -677,11 +677,11 @@ export const chartSetMixin = {
     /* 切换相关性图中英文 */
     changeRelevanceLang(){
         this.options.yAxis.forEach(item => {
-          item.title.text = this.currentLang == 'zh' ? item.title.textCh : item.title.textEn
+          item.title.text = this.currentLang == 'zh' ? item.title.textCh : item.title.textEn||item.title.textCh
         });
         //图例
         this.options.series.forEach(item => {
-          item.name = this.currentLang == 'zh' ? item.nameCh : item.nameEn
+          item.name = this.currentLang == 'zh' ? item.nameCh : item.nameEn||item.nameCh
         });
         //tooltip
         this.options.tooltip.formatter = this.currentLang == 'zh' ? this.options.tooltip.formatterCh : this.options.tooltip.formatterEn

+ 16 - 4
src/views/mychart_manage/components/chartDetailDia.vue

@@ -680,11 +680,15 @@ export default {
                   {
                     Value: res.Data.YDataList[0].Value,
                     Color:'#00f',
-                    Name:res.Data.ChartInfo.ChartName,
-                    NameEn:res.Data.ChartInfo.ChartNameEn
+                    Name:res.Data.YDataList[0].Name||res.Data.ChartInfo.ChartName,
+                    NameEn:res.Data.YDataList[0].NameEn||res.Data.ChartInfo.ChartNameEn
                   }
                 ]
             }
+            //多因子
+            if(res.Data.CorrelationChartInfo.AnalysisMode===1){
+                this.relevanceChartData.YDataList = res.Data.YDataList
+            }
             
             this.initRelevanceChartData()
             this.tableData = res.Data.EdbInfoList
@@ -1315,12 +1319,19 @@ export default {
     editChartHandle() {
       
       let path = '';
+      let type = '';
       if(this.chartInfo.Source === 1){
         path='/editchart'
       }else if([2,5].includes(this.chartInfo.Source)){
         path='/addCommodityChart'
       }else if([3,4].includes(this.chartInfo.Source)){
-        path='/relevancechartEditor'
+        //path='/relevancechartEditor'
+        path='/relevancechartEditorV2'
+        if(this.chartInfo.Source===3){
+            type = this.relevanceChartData.CorrelationChartInfo&&this.relevanceChartData.CorrelationChartInfo.AnalysisMode||0
+        }else{//滚动相关性也是单因子
+            type = 0
+        }
       }else if(this.chartInfo.Source===6){
         path='/fittingEquationChartEditor'
       }else if([7,8,9].includes(this.chartInfo.Source)) {
@@ -1334,7 +1345,8 @@ export default {
         path,
         query: {
           code: this.chartInfo.UniqueCode,
-          from: 'mychart'
+          from: 'mychart',
+          type,
         }
       })
       window.open(href,'_blank');

+ 15 - 11
src/views/ppt_manage/mixins/pptMixins.js

@@ -306,17 +306,21 @@ export default {
       }else if([3].includes(this.chartInfo.Source)){//相关性
         this.relevanceChartData={
             ChartInfo:res.Data.ChartInfo,
-						EdbInfoList:res.Data.EdbInfoList,
-						XEdbIdValue:this.chartInfo.Source === 3 ? res.Data.XEdbIdValue : res.Data.DataResp.XDateTimeValue,
-						CorrelationChartInfo:res.Data.CorrelationChartInfo,
-						YDataList:[
-							{
-								Value:this.chartInfo.Source === 3 ? res.Data.YDataList[0].Value : res.Data.DataResp.YDataList[0].Value,
-								Color:'#00f',
-								Name:res.Data.ChartInfo.ChartName,
-								NameEn:res.Data.ChartInfo.ChartNameEn
-							}
-						]
+            EdbInfoList:res.Data.EdbInfoList,
+            XEdbIdValue:this.chartInfo.Source === 3 ? res.Data.XEdbIdValue : res.Data.DataResp.XDateTimeValue,
+            CorrelationChartInfo:res.Data.CorrelationChartInfo,
+            YDataList:[
+                {
+                    Value:this.chartInfo.Source === 3 ? res.Data.YDataList[0].Value : res.Data.DataResp.YDataList[0].Value,
+                    Color:res.Data.YDataList[0].Color||'#00f',
+                    Name:res.Data.YDataList[0].Name||res.Data.ChartInfo.ChartName,
+                    NameEn:res.Data.YDataList[0].NameEn||res.Data.ChartInfo.ChartNameEn
+                }
+            ]
+        }
+        //多因子重新赋值YDataList
+        if(res.Data.CorrelationChartInfo.AnalysisMode===1){
+            this.relevanceChartData.YDataList = res.Data.YDataList
         }
         this.initRelevanceChartData()
         this.changeRelevanceOptions(currentLang)

+ 2 - 1
src/views/ppt_manage/newVersion/utils/untils.js

@@ -721,6 +721,7 @@ export const getContextMenuPos = (layerId) =>{
     Source:1
     MyChartType: 2 季节性图,7 柱形图,  10 截面散点图,
     Source:2 商品价格曲线图
+    Source:3,4 相关性/滚动相关性
     Source:5 利润曲线
     Source:6 拟合方程曲线
     Source:7 统计特征-标准差
@@ -730,7 +731,7 @@ export const getContextMenuPos = (layerId) =>{
 */
 export const isShowPPTTitle = (Source,MyChartType)=>{
     const sense_1 = Source===1&&[2,7,10].includes(MyChartType)
-    const sense_2 = [2,5,6,7,8,9].includes(Source)
+    const sense_2 = [2,3,4,5,6,7,8,9].includes(Source)
     return sense_1||sense_2
 }