Browse Source

Merge branch 'master' into hotfix/edb_calculate_tbz

xyxie 6 months ago
parent
commit
7d9273ad16
36 changed files with 7878 additions and 105 deletions
  1. 3 0
      controllers/business_conf.go
  2. 12 0
      controllers/data_manage/chart_common.go
  3. 1 35
      controllers/data_manage/chart_info.go
  4. 414 32
      controllers/data_manage/excel/excel_info.go
  5. 832 0
      controllers/data_manage/range_analysis/chart_classify.go
  6. 2052 0
      controllers/data_manage/range_analysis/chart_info.go
  7. 23 0
      controllers/report_v2.go
  8. 5 0
      go.mod
  9. 11 0
      go.sum
  10. 438 0
      models/data_manage/chart_info_range_analysis.go
  11. 82 0
      models/data_manage/excel/excel_info_rule_mapping.go
  12. 111 1
      models/data_manage/excel/request/excel_info.go
  13. 7 2
      models/data_manage/excel/request/mixed_table.go
  14. 7 1
      models/data_manage/excel/response/excel_info.go
  15. 62 0
      models/data_manage/factor_edb_series.go
  16. 210 0
      models/data_manage/factor_edb_series_calculate_data_qjjs.go
  17. 8 0
      models/data_manage/factor_edb_series_chart_mapping.go
  18. 7 0
      models/data_manage/factor_edb_series_mapping.go
  19. 52 1
      models/data_manage/multiple_graph_config_edb_mapping.go
  20. 15 13
      models/db.go
  21. 243 0
      routers/commentsRouter.go
  22. 7 0
      routers/router.go
  23. 384 3
      services/data/excel/excel_info.go
  24. 26 2
      services/data/excel/mixed_table.go
  25. 11 4
      services/data/excel/time_table.go
  26. 2247 0
      services/data/range_analysis/chart_info.go
  27. 49 2
      services/excel/lucky_sheet.go
  28. 224 0
      services/excel/lucky_sheet_table.go
  29. 88 0
      services/file.go
  30. 16 0
      services/report_v2.go
  31. 8 8
      services/smart_report.go
  32. 2 1
      static/ErrMsgConfig.json
  33. BIN
      static/SimHei.gob
  34. 144 0
      utils/common.go
  35. 3 0
      utils/constants.go
  36. 74 0
      utils/time.go

+ 3 - 0
controllers/business_conf.go

@@ -199,6 +199,9 @@ func (this *BusinessConfController) Save() {
 			return
 		}
 	}
+	// 删除研报声明缓存
+	disclaimerCacheKey := "hongze_yb:business_conf:disclaimer"
+	_ = utils.Rc.Delete(disclaimerCacheKey)
 
 	// 操作日志
 	go func() {

+ 12 - 0
controllers/data_manage/chart_common.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
+	"eta/eta_api/controllers/data_manage/range_analysis"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
@@ -167,6 +168,17 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_RANGE_ANALYSIS:
+		resp, isOk, msg, errMsg := range_analysis.GetChartInfoDetailFromUniqueCode(chartInfo, isCache, sysUser)
+		if !isOk {
+			br.Msg = msg
+			br.ErrMsg = errMsg
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
 	default:
 		br.Msg = "错误的图表"
 		br.ErrMsg = "错误的图表"

+ 1 - 35
controllers/data_manage/chart_info.go

@@ -18,7 +18,6 @@ import (
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"os"
 	"os/exec"
-	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -92,40 +91,7 @@ func (this *ChartInfoController) ChartInfoSave() {
 		req.Calendar = "公历"
 	}
 
-	var edbInfoIdArr []int
-	edbList := make([]*data_manage.EdbInfo, 0)
-
-	for _, v := range req.ChartEdbInfoList {
-		edbInfo, err := data_manage.GetEdbInfoById(v.EdbInfoId)
-		if err != nil {
-			if err.Error() == utils.ErrNoRow() {
-				br.Msg = "图表不存在!"
-				br.ErrMsg = "图表不存在,ChartInfoId:" + strconv.Itoa(v.EdbInfoId)
-				return
-			} else {
-				br.Msg = "获取图表信息失败!"
-				br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
-				return
-			}
-		}
-
-		if edbInfo == nil {
-			br.Msg = "指标不存在!"
-			br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(v.EdbInfoId)
-			return
-		}
-
-		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
-		edbInfo.EdbNameSource = edbInfo.EdbName
-		edbList = append(edbList, edbInfo)
-	}
-	sort.Ints(edbInfoIdArr)
-	var edbInfoIdArrStr []string
-	for _, v := range edbInfoIdArr {
-		edbInfoIdArrStr = append(edbInfoIdArrStr, strconv.Itoa(v))
-	}
-	edbInfoIdStr := strings.Join(edbInfoIdArrStr, ",")
-	err = data_manage.ModifyChartInfoAndMapping(edbInfoIdStr, &req, chartItem.ChartType)
+	err = data_manage.ModifyChartInfoAndMapping(chartItem.EdbInfoIds, &req, chartItem.ChartType)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存失败,Err:" + err.Error()

+ 414 - 32
controllers/data_manage/excel/excel_info.go

@@ -16,15 +16,16 @@ import (
 	"eta/eta_api/services/excel"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"io"
 	"os"
 	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
 )
 
 // ExcelInfoController ETA表格管理
@@ -159,30 +160,31 @@ func (c *ExcelInfoController) Add() {
 	if req.Source == utils.TIME_TABLE {
 		jsonStrByte, err := json.Marshal(req.TableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		var tableData request.TableDataReq
 		err = json.Unmarshal(jsonStrByte, &tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 
 		tableDataConfig, err := excel2.GetTableDataConfig(tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		edbInfoIdList = tableDataConfig.EdbInfoIdList
 
+		tableDataConfig.DecimalConfig = tableData.DecimalConfig
 		contentByte, err := json.Marshal(tableDataConfig)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取后,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		content = string(contentByte)
@@ -675,21 +677,21 @@ func (c *ExcelInfoController) Detail() {
 	if excelDetail.Source == utils.TIME_TABLE {
 		jsonStrByte, err := json.Marshal(excelDetail.TableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		var tableData request.TableDataReq
 		err = json.Unmarshal(jsonStrByte, &tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList(tableData.EdbInfoIdList)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		excelDetail.ExcelSource = strings.Join(sourceNameList, ",")
@@ -936,28 +938,29 @@ func (c *ExcelInfoController) Edit() {
 	case utils.TIME_TABLE: // 自定义表格
 		jsonStrByte, err := json.Marshal(req.TableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		var tableData request.TableDataReq
 		err = json.Unmarshal(jsonStrByte, &tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		edbInfoIdList = tableData.EdbInfoIdList
 		tableDataConfig, err := excel2.GetTableDataConfig(tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
+		tableDataConfig.DecimalConfig = tableData.DecimalConfig
 		contentByte, err := json.Marshal(tableDataConfig)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取后,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		content = string(contentByte)
@@ -1643,7 +1646,10 @@ func (c *ExcelInfoController) GetExcelTableData() {
 	}
 
 	tableData = excel.HandleTableCell(tableData)
-
+	tableData, err = excel.HandleRuleToTableCell(excelInfo.ExcelInfoId, tableData)
+	if err != nil {
+		utils.FileLog.Info("表格管理规则处理失败,HandleRuleToTableCell err:", err.Error())
+	}
 	config := response.ExcelTableDetailConfigResp{
 		FontSize: 9,
 	}
@@ -1835,7 +1841,7 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 		return
 	}
 
-	dataList, err := excel2.GetFirstEdbDataList(edbInfo, num, []string{})
+	dataList, err := excel2.GetFirstEdbDataList(edbInfo, num, []string{}, -1)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1857,6 +1863,10 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 	}
 	excelSource := strings.Join(sourceNameList, ",")
 	excelSourceEn := strings.Join(sourceNameEnList, ",")
+	if len(dataList) == 0 {
+		br.Msg = "指标数据异常,请检查"
+		return
+	}
 
 	br.Ret = 200
 	br.Success = true
@@ -1864,6 +1874,7 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 	br.Data = response.TableDataItem{
 		EdbInfoId:     edbInfoId,
 		Data:          dataList,
+		Decimal:       -1,
 		ExcelSource:   excelSource,
 		ExcelSourceEn: excelSourceEn,
 	}
@@ -1914,7 +1925,7 @@ func (c *ExcelInfoController) GetOtherEdbData() {
 		br.ErrMsg = fmt.Sprint("获取指标信息失败,Err:", err.Error())
 		return
 	}
-	dataList, err := excel2.GetOtherEdbDataList(edbInfo, req.DateList)
+	dataList, err := excel2.GetOtherEdbDataListFollowDate(edbInfo, req.DateList, req.DateDecimal)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1943,11 +1954,148 @@ func (c *ExcelInfoController) GetOtherEdbData() {
 	br.Data = response.TableDataItem{
 		EdbInfoId:     req.EdbInfoId,
 		Data:          dataList,
+		Decimal:       -1,
 		ExcelSource:   excelSource,
 		ExcelSourceEn: excelSourceEn,
 	}
 }
 
+// GetBatchEdbData
+// @Title 批量获取指定日期数据接口
+// @Description 批量获取未来日期数据接口
+// @Param	request	body request.GetBatchEdbDateDataReq true "type json string"
+// @router /excel_info/table/batch_edb_data_list [post]
+func (c *ExcelInfoController) GetBatchEdbData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.GetBatchEdbDateDataReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.EdbInfoId) == 0 {
+		br.Msg = "请选择指标"
+		br.IsSendEmail = false
+		return
+	}
+	tableList := make([]*response.TableDataItem, 0)
+	var firstDataList []string
+	for i, v := range req.EdbInfoId {
+		if req.Num <= 0 {
+			br.Msg = "期数必须大于0"
+			br.IsSendEmail = false
+			return
+		}
+		edbInfo, err := data_manage.GetEdbInfoById(v)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprint("获取指标信息失败,Err:", err.Error())
+			return
+		}
+		if len(req.DateList) == 0 {
+			req.Num = 12
+		}
+		if len(firstDataList) == 0 {
+			firstDataList = req.DateList
+		}
+		if len(firstDataList) != 0 && len(firstDataList) != req.Num {
+			req.Num = len(firstDataList)
+		}
+		if i == 0 {
+			dataList, err := excel2.GetFirstEdbDataList(edbInfo, req.Num, firstDataList, req.Decimal[i])
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
+				return
+			}
+
+			// 默认是倒序返回的数据,如果要正序的话,那么翻转下就好了
+			if req.SortType == "asc" {
+				for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+					dataList[i], dataList[j] = dataList[j], dataList[i]
+				}
+			}
+			dataList = excel2.PadFirstEdbDataList(dataList, req.DateList, req.SortType, req.Decimal[i])
+
+			sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{v})
+			if err != nil {
+				br.Msg = "时间序列表格数据获取失败"
+				br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
+				return
+			}
+			excelSource := strings.Join(sourceNameList, ",")
+			excelSourceEn := strings.Join(sourceNameEnList, ",")
+			if len(dataList) == 0 {
+				br.Msg = "指标数据异常,请检查"
+				return
+			}
+			tableList = append(tableList, &response.TableDataItem{
+				EdbInfoId:     v,
+				Data:          dataList,
+				Decimal:       req.Decimal[i],
+				ExcelSource:   excelSource,
+				ExcelSourceEn: excelSourceEn,
+			})
+			if len(firstDataList) == 0 {
+				tmpDataList := make([]string, 0)
+				for _, v := range dataList {
+					tmpDataList = append(tmpDataList, v.DataTime)
+				}
+				firstDataList = tmpDataList
+			}
+		} else {
+			dataList, err := excel2.GetOtherEdbDataList(edbInfo, firstDataList, req.Decimal[i])
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
+				return
+			}
+
+			// 默认是倒序返回的数据,如果要正序的话,那么翻转下就好了
+			if req.SortType == "asc" {
+				for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+					dataList[i], dataList[j] = dataList[j], dataList[i]
+				}
+			}
+
+			sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{v})
+			if err != nil {
+				br.Msg = "自定义表格数据获取失败"
+				br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+				return
+			}
+			excelSource := strings.Join(sourceNameList, ",")
+			excelSourceEn := strings.Join(sourceNameEnList, ",")
+			tableList = append(tableList, &response.TableDataItem{
+				EdbInfoId:     v,
+				Decimal:       req.Decimal[i],
+				Data:          dataList,
+				ExcelSource:   excelSource,
+				ExcelSourceEn: excelSourceEn,
+			})
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = tableList
+}
+
 // GetFutureDateData
 // @Title 获取未来日期数据接口
 // @Description 获取未来日期数据接口
@@ -2091,7 +2239,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 		for i := lenDate - 1; i >= 0; i-- {
 			dateStrList = append(dateStrList, dateList[i].Format(utils.FormatDate))
 		}
-		for _, v := range req.EdbInfoIdList {
+		for j, v := range req.EdbInfoIdList {
 			tmpEdbInfo, ok := edbInfoMap[v]
 			if !ok {
 				br.Msg = "获取指标信息失败"
@@ -2099,7 +2247,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 				return
 			}
 
-			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
+			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList, req.Decimal[j])
 			if err != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -2107,6 +2255,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 			}
 			result = append(result, response.TableDataItem{
 				EdbInfoId: v,
+				Decimal:   req.Decimal[j],
 				Data:      dataList,
 			})
 		}
@@ -2182,7 +2331,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 			br.ErrMsg = "获取指标信息失败,err:" + err.Error()
 			return
 		}
-		firstDataList, err := excel2.GetFirstHistoryEdbDataList(tmpEdbInfo, req.Num, req.EndDate)
+		firstDataList, err := excel2.GetFirstHistoryEdbDataList(tmpEdbInfo, req.Num, req.EndDate, req.Decimal[0])
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -2191,6 +2340,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 
 		result = append(result, response.TableDataItem{
 			EdbInfoId: req.EdbInfoIdList[0],
+			Decimal:   req.Decimal[0],
 			Data:      firstDataList,
 		})
 
@@ -2211,7 +2361,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 				return
 			}
 
-			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
+			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList, req.Decimal[k])
 			if err != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -2219,6 +2369,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 			}
 			result = append(result, response.TableDataItem{
 				EdbInfoId: v,
+				Decimal:   req.Decimal[k],
 				Data:      dataList,
 			})
 		}
@@ -2966,3 +3117,234 @@ func (c *ExcelInfoController) GetEdbSource() {
 	br.Ret = 200
 	br.Success = true
 }
+
+// ExcelRule
+// @Title 添加表格规则
+// @Description 添加表格规则
+// @Param	request	body excel3.BatchRefreshExcelReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/rule/add [post]
+func (c *ExcelInfoController) AddExcelRule() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req *request.ExcelRuleMappingReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.LeftValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	if req.Scope == "" {
+		br.Msg = "应用选区不能为空"
+		return
+	}
+	if req.FontColor == "" && req.BackgroundColor == "" {
+		br.Msg = "字体颜色和背景颜色不能同时为空"
+		return
+	}
+	if req.RuleType == 3 && req.RightValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	err = excel2.AddExcelRule(req, c.Lang)
+	if err != nil {
+		br.Msg = "规则添加失败"
+		br.ErrMsg = "规则添加失败,Err:" + err.Error()
+		return
+	}
+	br.Msg = "添加成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// EditExcelRule
+// @Title 编辑表格规则
+// @Description 编辑表格规则
+// @Param	request	body request.ExcelRuleMappingReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/rule/edit [post]
+func (c *ExcelInfoController) EditExcelRule() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req *request.ExcelRuleMappingReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.LeftValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	if req.Scope == "" {
+		br.Msg = "应用选区不能为空"
+		return
+	}
+	if req.FontColor == "" {
+		br.Msg = "字体颜色不能为空"
+		return
+	}
+	if req.BackgroundColor == "" {
+		br.Msg = "背景颜色不能为空"
+		return
+	}
+	if req.RuleType == 3 && req.RightValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	if req.ExcelRuleMappingId <= 0 {
+		br.Msg = "非法的管理规则ID"
+		return
+	}
+	err = excel2.ModifyExcelRule(req, c.Lang)
+	if err != nil {
+		br.Msg = "规则编辑失败"
+		br.ErrMsg = "规则编辑失败,Err:" + err.Error()
+		return
+	}
+	br.Msg = "编辑成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// DeleteExcelRule
+// @Title 删除表格规则
+// @Description 删除表格规则
+// @Param	request	body request.ExcelRuleMappingReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/rule/delete [post]
+func (c *ExcelInfoController) DeleteExcelRule() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.DeleteExcelRuleMappingReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ExcelRuleMappingId <= 0 {
+		br.Msg = "非法的管理规则ID"
+		return
+	}
+	err = excel3.DeleteExcelRuleMappingById(req.ExcelRuleMappingId)
+	if err != nil {
+		br.Msg = "规则删除失败"
+		br.ErrMsg = "规则删除失败,Err:" + err.Error()
+		return
+	}
+	br.Msg = "删除成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetExcelRuleList
+// @Title 表格规则列表
+// @Description 表格规则列表
+// @Param   ExcelInfoId   query   int  true       "id"
+// @Success Ret=200 获取成功
+// @router /excel_info/rule/list [get]
+func (c *ExcelInfoController) GetExcelRuleList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	ExcelInfoId, _ := c.GetInt("ExcelInfoId")
+
+	if ExcelInfoId <= 0 {
+		br.Msg = "请选择表格"
+		return
+	}
+
+	items, err := excel2.GetExcelRuleList(ExcelInfoId)
+	if err != nil {
+		br.Msg = "管理规则编辑失败"
+		br.ErrMsg = "管理规则添加失败,Err:" + err.Error()
+		return
+	}
+	if items.List == nil {
+		items.List = []*excel3.ExcelInfoRuleMappingView{}
+	}
+
+	br.Data = items
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetExcelRuleDetail
+// @Title 表格规则详情
+// @Description 表格规则详情
+// @Param   ExcelInfoRuleMappingId   query   int  true       "id"
+// @Success Ret=200 获取成功
+// @router /excel_info/rule/detail [get]
+func (c *ExcelInfoController) GetExcelRuleDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	excelInfoRuleMappingId, _ := c.GetInt("ExcelInfoRuleMappingId")
+
+	items, err := excel2.GetExcelRuleDetail(excelInfoRuleMappingId)
+	if err != nil {
+		br.Msg = "管理规则编辑失败"
+		br.ErrMsg = "管理规则添加失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = items
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 832 - 0
controllers/data_manage/range_analysis/chart_classify.go

@@ -0,0 +1,832 @@
+package range_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/data_manage_permission"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"time"
+)
+
+// RangeChartClassifyController 	区间分析图表
+type RangeChartClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// ChartClassifyList
+// @Title 区间分析图表分类列表
+// @Description 区间分析图表分类列表接口
+// @Param   IsShowMe   query   bool  false       "是否只看我的,true、false"
+// @Param   ParentId   query   bool  false       "父级ID"
+// @Param   Source   query   int  false       "图表类型,3:相关性,4:滚动相关性"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /chart_classify/list [get]
+func (this *RangeChartClassifyController) ChartClassifyList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	resp := new(data_manage.ChartClassifyListResp)
+
+	// 获取当前账号的不可见指标
+	//noPermissionChartIdMap := make(map[int]bool)
+	//{
+	//	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	//	confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+	//	if err != nil && err.Error() != utils.ErrNoRow() {
+	//		br.Msg = "获取失败"
+	//		br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+	//		return
+	//	}
+	//	for _, v := range confList {
+	//		noPermissionChartIdMap[v.ChartInfoId] = true
+	//	}
+	//}
+
+	isShowMe, _ := this.GetBool("IsShowMe")
+	parentId, _ := this.GetInt("ParentId")
+	source, _ := this.GetInt("Source", utils.CHART_SOURCE_RANGE_ANALYSIS)
+
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	// 查询分类节点
+	rootList, err := data_manage.GetChartClassifyByParentId(parentId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	if len(rootList) > 0 {
+		permissionClassifyIdList, e := data_manage_permission.GetUserChartClassifyPermissionList(this.SysUser.AdminId, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取已授权分类id数据失败,Err:" + e.Error()
+			return
+		}
+
+		for _, v := range rootList {
+			// 操作按钮权限
+			v.HaveOperaAuth = data_manage_permission.CheckChartClassifyPermissionByPermissionIdList(v.IsJoinPermission, v.ChartClassifyId, permissionClassifyIdList)
+			button := data.GetChartClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			v.Button = button
+			v.ParentId = parentId
+			v.Children = make([]*data_manage.ChartClassifyItems, 0)
+
+			nodeAll = append(nodeAll, v)
+		}
+	}
+
+	// 查询图表节点, ParentId=0时说明仅查询一级目录节点
+	if parentId > 0 {
+		// 查询当前分类信息
+		currClassify, e := data_manage.GetChartClassifyById(parentId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取当前分类信息失败,Err:" + e.Error()
+			return
+		}
+
+		// 获取所有有权限的指标和分类
+		permissionEdbIdList, permissionClassifyIdList, e := data_manage_permission.GetUserChartAndClassifyPermissionList(this.SysUser.AdminId, 0, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + e.Error()
+			return
+		}
+
+		var adminId int
+		if isShowMe {
+			adminId = this.SysUser.AdminId
+		}
+
+		charts, e := data_manage.GetChartInfoBySourceAndParentId(source, parentId, adminId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图表信息失败, Err: %v", e)
+			return
+		}
+		for _, v := range charts {
+			// 操作按钮权限
+			v.HaveOperaAuth = data_manage_permission.CheckChartPermissionByPermissionIdList(v.IsJoinPermission, currClassify.IsJoinPermission, v.ChartInfoId, v.ChartClassifyId, permissionEdbIdList, permissionClassifyIdList)
+			button := data.GetChartOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			button.AddButton = false //不管有没有权限,图表都是没有添加按钮的
+			v.Button = button
+			v.ParentId = parentId
+			v.Children = make([]*data_manage.ChartClassifyItems, 0)
+
+			nodeAll = append(nodeAll, v)
+		}
+	}
+
+	// 整体排序
+	if len(nodeAll) > 0 {
+		sort.Slice(nodeAll, func(i, j int) bool {
+			return nodeAll[i].Sort < nodeAll[j].Sort
+		})
+	}
+
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// getChartClassifyListForMe 获取我创建的图表
+func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartClassifyListResp) (errMsg string, err error) {
+	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	classifyAll, err := data_manage.GetChartClassifyAll(utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	allChartInfo, err := data_manage.GetChartInfoByAdminId([]int{utils.CHART_SOURCE_RANGE_ANALYSIS, utils.CHART_SOURCE_RANGE_ANALYSIS}, adminInfo.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	chartInfoMap := make(map[int][]*data_manage.ChartClassifyItems)
+	for _, v := range allChartInfo {
+		chartInfoMap[v.ChartClassifyId] = append(chartInfoMap[v.ChartClassifyId], v)
+	}
+	rootChildMap := make(map[int][]*data_manage.ChartClassifyItems)
+	for _, v := range classifyAll {
+		rootChildMap[v.ParentId] = append(rootChildMap[v.ParentId], v)
+		if existItems, ok := chartInfoMap[v.ChartClassifyId]; ok {
+			v.Children = existItems
+		} else {
+			items := make([]*data_manage.ChartClassifyItems, 0)
+			v.Children = items
+		}
+	}
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range rootList {
+		if existItems, ok := rootChildMap[v.ChartClassifyId]; ok {
+			v.Children = existItems
+		} else {
+			items := make([]*data_manage.ChartClassifyItems, 0)
+			v.Children = items
+		}
+		nodeAll = append(nodeAll, v)
+	}
+	resp.AllNodes = nodeAll
+
+	return
+}
+
+// ChartClassifyItems
+// @Title 获取所有区间分析图表分类接口-不包含图表
+// @Description 获取所有区间分析图表分类接口-不包含图表
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /chart_classify/items [get]
+func (this *RangeChartClassifyController) ChartClassifyItems() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		nodeAll = append(nodeAll, rootNode)
+	}
+	resp := new(data_manage.ChartClassifyListResp)
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// AddChartClassify
+// @Title 新增区间分析图表分类
+// @Description 新增区间分析图表分类接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chart_classify/add [post]
+func (this *RangeChartClassifyController) AddChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.AddChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 新增图表分类
+	_, err, errMsg, isSendEmail := data.AddChartClassify(req.ChartClassifyName, req.ParentId, req.Level, utils.CHART_SOURCE_RANGE_ANALYSIS, this.Lang, this.SysUser)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "添加分类失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "添加成功"
+	br.Success = true
+}
+
+// EditChartClassify
+// @Title 修改区间分析图表分类
+// @Description 修改区间分析图表分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /chart_classify/edit [post]
+func (this *RangeChartClassifyController) EditChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.EditChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 编辑图表分类
+	_, err, errMsg, isSendEmail := data.EditChartClassify(req.ChartClassifyId, utils.CHART_SOURCE_RANGE_ANALYSIS, req.ChartClassifyName, this.Lang, this.SysUser)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "修改成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// DeleteChartClassifyCheck
+// @Title 删除图表检测接口
+// @Description 删除图表检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /chart_classify/delete/check [post]
+func (this *RangeChartClassifyController) DeleteChartClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.ChartClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartClassifyId < 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+	//删除分类
+	if req.ChartClassifyId > 0 && req.ChartInfoId == 0 {
+		//判断区间分析图表分类下,是否含有图表
+		count, err := data_manage.GetChartInfoCountByClassifyId(req.ChartClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联图表不可删除"
+		}
+	}
+
+	if deleteStatus != 1 && req.ChartInfoId == 0 {
+		classifyCount, err := data_manage.GetChartClassifyCountByClassifyId(req.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+		if classifyCount > 0 {
+			deleteStatus = 2
+			tipsMsg = "确认删除当前目录及包含的子目录吗"
+		}
+	}
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+
+	resp := new(data_manage.ChartClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// DeleteChartClassify
+// @Title 删除区间分析图表分类/图表
+// @Description 删除区间分析图表分类/图表接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /chart_classify/delete [post]
+func (this *RangeChartClassifyController) DeleteChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req data_manage.DeleteChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartClassifyId < 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	//删除分类
+	if req.ChartClassifyId > 0 && req.ChartInfoId == 0 {
+		//判断是否含有指标
+		count, err := data_manage.GetChartInfoCountByClassifyId(req.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			br.Msg = "该目录下存在关联指标,不可删除"
+			br.IsSendEmail = false
+			return
+		}
+
+		err = data_manage.DeleteChartClassify(req.ChartClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+	resp := new(data_manage.AddChartInfoResp)
+	//删除图表
+	if req.ChartInfoId > 0 {
+		chartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "图表已删除,请刷新页面"
+				br.ErrMsg = "指标不存在,Err:" + err.Error()
+				return
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,获取指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+		if chartInfo == nil {
+			br.Msg = "图表已删除,请刷新页面"
+			return
+		}
+		//图表操作权限
+		ok := data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+		if !ok {
+			br.Msg = "没有该图表的操作权限"
+			br.ErrMsg = "没有该图表的操作权限"
+			return
+		}
+
+		// 获取引用该图表的MyCharts, 用于ES删除
+		var myCond string
+		var myPars []interface{}
+		myCond += ` AND a.chart_info_id = ? `
+		myPars = append(myPars, chartInfo.ChartInfoId)
+		myCharts, e := data_manage.GetMyChartListGroupByCharyInfoIdAndAdminIdByCondition(myCond, myPars)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取引用图表的MyChats失败, Err: " + e.Error()
+			return
+		}
+		myIds := make([]int, 0)
+		for _, m := range myCharts {
+			myIds = append(myIds, m.MyChartId)
+		}
+
+		source := chartInfo.Source // 区间分析图表(滚动相关性)
+		//删除图表及关联指标
+		err = data_manage.DeleteChartInfoAndData(chartInfo.ChartInfoId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+
+		// 删除图表系列
+		chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+		seriesMappingItem, e := chartSeriesOb.GetItemByChartInfoId(chartInfo.ChartInfoId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取图表关联失败, Err: " + e.Error()
+				return
+			}
+		} else {
+			factorSeriesOb := new(data_manage.FactorEdbSeries)
+			e = factorSeriesOb.RemoveSeriesAndMappingByFactorEdbSeriesId(seriesMappingItem)
+			if e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取图表关联失败, Err: " + e.Error()
+				return
+			}
+		}
+		//删除ES
+		{
+			go data.EsDeleteChartInfo(chartInfo.ChartInfoId)
+			// 删除MY ETA 图表 es数据
+			//go data.EsDeleteMyChartInfoByChartInfoId(chartInfo.ChartInfoId)
+			go data.EsDeleteMyChartInfoByMyChartIds(myIds)
+		}
+		// 删除配置关联指标数据
+		multiConfig, e := data_manage.GetMultipleGraphConfigChartMappingByChartId(chartInfo.ChartInfoId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取图表关联配置失败, Err: " + e.Error()
+			return
+		}
+		if multiConfig != nil { // 删除配置关联指标数据
+			e = data_manage.DeleteMultipleGraphConfigByChartInfoId(chartInfo.ChartInfoId, multiConfig.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+			if e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除图表关联配置失败, Err: " + e.Error()
+				return
+			}
+		}
+
+		var condition string
+		var pars []interface{}
+		condition += " AND chart_classify_id=? AND source = ? "
+		pars = append(pars, chartInfo.ChartClassifyId, source)
+
+		condition += " AND chart_info_id>? ORDER BY create_time ASC LIMIT 1 "
+		pars = append(pars, req.ChartInfoId)
+
+		nextItem, err := data_manage.GetChartInfoByCondition(condition, pars)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
+			return
+		}
+
+		if nextItem != nil {
+			resp.UniqueCode = nextItem.UniqueCode
+			resp.ChartInfoId = nextItem.ChartInfoId
+		} else {
+			var condition string
+			var pars []interface{}
+
+			condition += " AND level=1 "
+			//pars = append(pars, chartInfo.ChartClassifyId)
+
+			condition += " AND chart_classify_id>? ORDER BY chart_classify_id ASC LIMIT 1 "
+			pars = append(pars, chartInfo.ChartClassifyId)
+
+			classifyItem, err := data_manage.GetChartClassifyByCondition(condition, pars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取下一级图库分类信息失败,Err:" + err.Error()
+				return
+			}
+			if classifyItem != nil {
+				nextItem, err = data_manage.GetNextChartInfo(chartInfo.ChartClassifyId)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
+					return
+				}
+				if nextItem != nil {
+					resp.UniqueCode = nextItem.UniqueCode
+					resp.ChartInfoId = nextItem.ChartInfoId
+				}
+			}
+		}
+		//新增操作日志
+		{
+			chartLog := new(data_manage.ChartInfoLog)
+			chartLog.ChartName = chartInfo.ChartName
+			chartLog.ChartInfoId = req.ChartInfoId
+			chartLog.ChartClassifyId = chartInfo.ChartClassifyId
+			chartLog.SysUserId = sysUser.AdminId
+			chartLog.SysUserRealName = sysUser.RealName
+			chartLog.UniqueCode = chartInfo.UniqueCode
+			chartLog.CreateTime = time.Now()
+			chartLog.Content = string(this.Ctx.Input.RequestBody)
+			chartLog.Status = "删除图表"
+			chartLog.Method = this.Ctx.Input.URI()
+			go data_manage.AddChartInfoLog(chartLog)
+		}
+	}
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// ChartClassifyMove
+// @Title 区间分析图表分类移动接口
+// @Description 区间分析图表分类移动接口
+// @Success 200 {object} data_manage.MoveChartClassifyReq
+// @router /chart_classify/move [post]
+func (this *RangeChartClassifyController) ChartClassifyMove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.MoveChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "请选择拖动目标,分类目录或者指标"
+		return
+	}
+
+	err, errMsg := data.MoveChartClassify(req, sysUser, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	//if req.ClassifyId <= 0 {
+	//	br.Msg = "参数错误"
+	//	br.ErrMsg = "分类id小于等于0"
+	//	return
+	//}
+	//
+	////判断分类是否存在
+	//chartClassifyInfo, err := data_manage.GetChartClassifyById(req.ClassifyId)
+	//if err != nil {
+	//	br.Msg = "移动失败"
+	//	br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+	//	return
+	//}
+	//if chartClassifyInfo.Source != utils.CHART_SOURCE_RANGE_ANALYSIS {
+	//	br.Msg = "分类异常"
+	//	br.ErrMsg = "分类异常,不是区间分析图表的分类"
+	//	return
+	//}
+	//updateCol := make([]string, 0)
+	//
+	////判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	//if chartClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+	//	parentChartClassifyInfo, err := data_manage.GetChartClassifyById(req.ParentClassifyId)
+	//	if err != nil {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+	//		return
+	//	}
+	//	chartClassifyInfo.ParentId = parentChartClassifyInfo.ChartClassifyId
+	//	chartClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+	//	chartClassifyInfo.ModifyTime = time.Now()
+	//	updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+	//}
+	//
+	////如果有传入 上一个兄弟节点分类id
+	//if req.PrevClassifyId > 0 {
+	//	//上一个兄弟节点
+	//	prevClassify, err := data_manage.GetChartClassifyById(req.PrevClassifyId)
+	//	if err != nil {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+	//		return
+	//	}
+	//
+	//	//如果是移动在两个兄弟节点之间
+	//	if req.NextClassifyId > 0 {
+	//		//下一个兄弟节点
+	//		nextClassify, err := data_manage.GetChartClassifyById(req.NextClassifyId)
+	//		if err != nil {
+	//			br.Msg = "移动失败"
+	//			br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+	//			return
+	//		}
+	//		//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+	//		if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == chartClassifyInfo.Sort {
+	//			//变更兄弟节点的排序
+	//			updateSortStr := `sort + 2`
+	//			_ = data_manage.UpdateChartClassifySortByParentId(prevClassify.ParentId, prevClassify.ChartClassifyId, prevClassify.Sort, updateSortStr)
+	//		} else {
+	//			//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+	//			if nextClassify.Sort-prevClassify.Sort == 1 {
+	//				//变更兄弟节点的排序
+	//				updateSortStr := `sort + 1`
+	//				_ = data_manage.UpdateChartClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+	//			}
+	//		}
+	//	}
+	//
+	//	chartClassifyInfo.Sort = prevClassify.Sort + 1
+	//	chartClassifyInfo.ModifyTime = time.Now()
+	//	updateCol = append(updateCol, "Sort", "ModifyTime")
+	//
+	//} else {
+	//	firstClassify, err := data_manage.GetFirstChartClassifyByParentId(chartClassifyInfo.ParentId)
+	//	if err != nil && err.Error() != utils.ErrNoRow() {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+	//		return
+	//	}
+	//
+	//	//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+	//	if firstClassify != nil && firstClassify.Sort == 0 {
+	//		updateSortStr := ` sort + 1 `
+	//		_ = data_manage.UpdateChartClassifySortByParentId(firstClassify.ParentId, firstClassify.ChartClassifyId-1, 0, updateSortStr)
+	//	}
+	//
+	//	chartClassifyInfo.Sort = 0 //那就是排在第一位
+	//	chartClassifyInfo.ModifyTime = time.Now()
+	//	updateCol = append(updateCol, "Sort", "ModifyTime")
+	//}
+	//
+	////更新
+	//if len(updateCol) > 0 {
+	//	err = chartClassifyInfo.Update(updateCol)
+	//	if err != nil {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "修改失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+// ClassifyTree
+// @Title 多层分类列表树
+// @Description 多层分类列表树
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /chart_classify/tree [get]
+func (this *RangeChartClassifyController) ClassifyTree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	allList, err := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取所有分类失败, Err:" + err.Error()
+		return
+	}
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+
+	if len(allList) > 0 {
+		// 已授权分类id
+		permissionClassifyIdList, e := data_manage_permission.GetUserChartClassifyPermissionList(this.SysUser.AdminId, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取已授权分类id数据失败,Err:" + e.Error()
+			return
+		}
+
+		for k, v := range allList {
+			// 数据权限
+			v.HaveOperaAuth = data_manage_permission.CheckChartClassifyPermissionByPermissionIdList(v.IsJoinPermission, v.ChartClassifyId, permissionClassifyIdList)
+			// 按钮权限
+			button := data.GetChartClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			allList[k].Button = button
+		}
+
+		nodeAll = data.GetChartClassifyTreeRecursive(allList, 0)
+		//根据sort值排序
+		sort.Slice(nodeAll, func(i, j int) bool {
+			return nodeAll[i].Sort < nodeAll[j].Sort
+		})
+	}
+
+	language := `CN`
+	// 显示的语言
+	{
+		configDetail, _ := system.GetConfigDetailByCode(this.SysUser.AdminId, system.ChartLanguageVar)
+		if configDetail != nil {
+			language = configDetail.ConfigValue
+		} else {
+			configDetail, _ = system.GetDefaultConfigDetailByCode(system.ChartLanguageVar)
+			if configDetail != nil {
+				language = configDetail.ConfigValue
+			}
+		}
+	}
+
+	// 是否允许添加一级分类
+	canOpClassify := true
+	button := data.GetChartClassifyOpButton(this.SysUser, 0, true)
+	if !button.AddButton {
+		canOpClassify = false
+	}
+
+	resp := new(data_manage.ChartClassifyListResp)
+	resp.AllNodes = nodeAll
+	resp.Language = language
+	resp.CanOpClassify = canOpClassify
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 2052 - 0
controllers/data_manage/range_analysis/chart_info.go

@@ -0,0 +1,2052 @@
+package range_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/chart_theme"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/data_manage_permission"
+	rangeServ "eta/eta_api/services/data/range_analysis"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// RangeChartChartInfoController 区间计算图表管理
+type RangeChartChartInfoController struct {
+	controllers.BaseAuthController
+}
+
+// Preview
+// @Title 区间计算图表-预览数据
+// @Description 区间计算图表-获取预览数据
+// @Param   StartDate	query	string	true	"自定义开始日期"
+// @Param   EndDate		query	string	true	"自定义结束日期"
+// @Param   LeadValue	query	int		true	"领先值"
+// @Param   LeadUnit	query	string	true	"领先单位:年,天,月,季,周"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview [post]
+func (this *RangeChartChartInfoController) Preview() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.ChartRangeAnalysisPreviewReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	// 基础校验
+	if len(req.ChartEdbInfoList) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	err, msg, isSendEmail := rangeServ.CheckChartRangeExtraConfig(req.ExtraConfig)
+	if err != nil {
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 获取指标信息
+	//chartInfo.CorrelationLeadUnit = req.LeadUnit
+	edbInfoMappingList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoMapping, e := data_manage.GetChartEdbMappingByEdbInfoId(v.EdbInfoId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = v.EdbAliasName + "指标不存在"
+				br.ErrMsg = v.EdbAliasName + "指标不存在"
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取区间计算图表, A指标mapping信息失败, Err:" + e.Error()
+			return
+		}
+		if v.EdbAliasName != "" {
+			edbInfoMapping.EdbAliasName = v.EdbAliasName
+		}
+		edbInfoMapping.IsAxis = v.IsAxis
+		edbInfoMappingList = append(edbInfoMappingList, edbInfoMapping)
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+	chartInfo.ChartType = 1
+	chartInfo.Source = utils.CHART_SOURCE_RANGE_ANALYSIS
+	chartThemeType, err := chart_theme.GetChartThemeTypeByChartTypeAndSource(chartInfo.ChartType, 1)
+	if err != nil {
+		return
+	}
+	chartTheme, err := data.GetChartThemeConfig(chartThemeType.DefaultChartThemeId, utils.CHART_SOURCE_DEFAULT, chartInfo.ChartType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	// 获取图表x轴y轴
+	edbList, xEdbIdValue, dataResp, e := rangeServ.GetChartDataByEdbInfoList(0, req.DateType, req.StartYear, req.StartDate, req.EndDate, edbInfoMappingList, &req.ExtraConfig)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取区间计算图表, 图表计算值失败, Err:" + e.Error()
+		return
+	}
+	//添加配置信息
+	if req.ExtraConfig.MultipleGraphConfigId == 0 {
+		multipleGraphConfig := &data_manage.MultipleGraphConfig{
+			//MultipleGraphConfigId: 0,
+			SysUserId:       sysUser.AdminId,
+			SysUserRealName: sysUser.RealName,
+			ModifyTime:      time.Now(),
+			CreateTime:      time.Now(),
+		}
+		err = data_manage.AddMultipleGraphConfig(multipleGraphConfig)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "新增区间计算图表配置失败, Err: " + err.Error()
+			return
+		}
+		dataResp.MultipleGraphConfigId = multipleGraphConfig.MultipleGraphConfigId
+	}
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.DataResp = dataResp
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增图表接口
+// @Description 新增图表接口
+// @Param	request	body data_manage.AddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/add [post]
+func (this *RangeChartChartInfoController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req data_manage.AddChartInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.DateType == 0 {
+		req.DateType = 3
+	}
+	chartInfo, err, errMsg, isSendEmail := rangeServ.AddChartInfo(req, utils.CHART_SOURCE_RANGE_ANALYSIS, sysUser, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartInfo.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "新增区间计算图表"
+		chartLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartInfo.ChartInfoId
+	resp.UniqueCode = chartInfo.UniqueCode
+	resp.ChartType = chartInfo.ChartType
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// Edit
+// @Title 编辑图表接口
+// @Description 编辑图表接口
+// @Param	request	body data_manage.EditChartInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /chart_info/edit [post]
+func (this *RangeChartChartInfoController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req data_manage.EditChartInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartItem, err, errMsg, isSendEmail := rangeServ.EditChartInfo(req, sysUser, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartItem.ChartInfoId
+	resp.UniqueCode = chartItem.UniqueCode
+	resp.ChartType = req.ChartType
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑区间计算图表"
+		chartLog.Method = this.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// Detail
+// @Title 获取图表详情
+// @Description 获取图表详情接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   DateType   query   int  true       "日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"
+// @Param   StartDate   query   string  true       "自定义开始日期"
+// @Param   EndDate   query   string  true       "自定义结束日期"
+// @Param   Calendar   query   string  true       "公历/农历"
+// @Param   SeasonStartDate   query   string  true       "季节性图开始日期"
+// @Param   SeasonEndDate   query   string  true       "季节性图结束日期"
+// @Param   EdbInfoId   query   string  true       "指标ID,多个用英文逗号隔开"
+// @Param   ChartType   query   int  true       "生成样式:1:曲线图,2:季节性图"
+// @Success 200 {object} data_manage.ChartInfoDetailResp
+// @router /chart_info/detail [get]
+func (this *RangeChartChartInfoController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	if chartInfoId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	dateType, _ := this.GetInt("DateType")
+	fmt.Println("dateType:", dateType)
+
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	startYear, _ := this.GetInt("StartYear")
+
+	var err error
+	chartInfo := new(data_manage.ChartInfoView)
+	chartInfo, err = data_manage.GetChartInfoViewById(chartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图被删除,请刷新页面"
+			br.ErrMsg = "图被删除,请刷新页面,Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+	if dateType <= 0 {
+		dateType = chartInfo.DateType
+	}
+	if startDate == "" {
+		startDate = chartInfo.StartDate
+	}
+	if endDate == "" {
+		endDate = chartInfo.EndDate
+	}
+
+	if startYear <= 0 {
+		startYear = chartInfo.StartYear
+	}
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, chartInfo.ChartType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
+	// 获取指标信息
+	//chartInfo.CorrelationLeadUnit = req.LeadUnit
+	edbInfoMappingList, err := data_manage.GetChartEdbMappingList(chartInfoId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	// 获取图表数据
+	if len(edbInfoMappingList) == 0 {
+		br.Msg = "获取失败"
+		br.ErrMsg = "图表没有指标,无法计算"
+		return
+	}
+
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if chartInfo.ExtraConfig == `` {
+		br.Msg = "配置信息错误"
+		return
+	}
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		br.Msg = "配置信息错误"
+		br.ErrMsg = "图表配置信息错误,Err:" + err.Error()
+		return
+	}
+
+	// 获取图表x轴y轴
+	edbList, xEdbIdValue, dataResp, e := rangeServ.GetChartDataByEdbInfoList(chartInfoId, dateType, startYear, startDate, endDate, edbInfoMappingList, &extraConfig)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取区间计算图表, 图表计算值失败, Err:" + e.Error()
+		return
+	}
+
+	// 判断是否加入我的图库
+	if chartInfoId > 0 && chartInfo != nil {
+		{
+			var myChartCondition string
+			var myChartPars []interface{}
+			myChartCondition += ` AND a.admin_id=? `
+			myChartPars = append(myChartPars, sysUser.AdminId)
+			myChartCondition += ` AND a.chart_info_id=? `
+			myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+			myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+				return
+			}
+			if myChartList != nil && len(myChartList) > 0 {
+				chartInfo.IsAdd = true
+				chartInfo.MyChartId = myChartList[0].MyChartId
+				chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+			}
+		}
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    chartInfo.IsEdit,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	// 指标权限
+	{
+		edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
+		classifyMap := make(map[int]*data_manage.EdbClassify)
+		// 分类
+		{
+			classifyIdList := make([]int, 0)
+			for _, v := range edbList {
+				classifyIdList = append(classifyIdList, v.ClassifyId)
+			}
+			classifyList, tmpErr := data_manage.GetEdbClassifyByIdList(classifyIdList)
+			if tmpErr != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类列表失败,Err:" + tmpErr.Error()
+				return
+			}
+			for _, v := range classifyList {
+				classifyMap[v.ClassifyId] = v
+			}
+		}
+
+		// 遍历到校验map
+		for _, v := range edbList {
+			edbClassifyPermissionMap[v.EdbInfoId] = data_manage_permission.EdbClassifyPermission{
+				ClassifyId:       v.ClassifyId,
+				IsJoinPermission: v.IsJoinPermission,
+				EdbInfoId:        v.EdbInfoId,
+			}
+		}
+
+		// 获取所有有权限的指标和分类
+		permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserEdbAndClassifyPermissionList(sysUser.AdminId, 0, 0)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+			return
+		}
+
+		for _, v := range edbList {
+			// 数据权限
+			edbItem, ok := edbClassifyPermissionMap[v.EdbInfoId]
+			if !ok {
+				continue
+			}
+
+			if currClassify, ok := classifyMap[edbItem.ClassifyId]; ok {
+				v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(edbItem.IsJoinPermission, currClassify.IsJoinPermission, edbItem.EdbInfoId, edbItem.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
+			}
+		}
+	}
+
+	// 图表当前分类的分类树
+	classifyLevels := make([]string, 0)
+	{
+		list, e := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_RANGE_ANALYSIS)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", e)
+			return
+		}
+		parents := data.GetChartClassifyParentRecursive(list, chartInfo.ChartClassifyId)
+		sort.Slice(parents, func(i, j int) bool {
+			return parents[i].Level < parents[i].Level
+		})
+		for _, v := range parents {
+			classifyLevels = append(classifyLevels, v.UniqueCode)
+		}
+	}
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.DataResp = dataResp
+	resp.ClassifyLevels = classifyLevels
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// DetailFromUniqueCode
+// @Title 根据编码获取图表详情
+// @Description 根据编码获取图表详情接口
+// @Param   UniqueCode   query   int  true       "图表唯一编码,如果是管理后台访问,传固定字符串:7c69b590249049942070ae9dcd5bf6dc"
+// @Param   IsCache   query   bool  true       "是否走缓存,默认false"
+// @Success 200 {object} data_manage.ChartInfoDetailFromUniqueCodeResp
+// @router /chart_info/detail/from_unique_code [get]
+func (this *RangeChartChartInfoController) DetailFromUniqueCode() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	adminId := sysUser.AdminId
+
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,uniqueCode is empty"
+		return
+	}
+
+	//是否走缓存
+	isCache, _ := this.GetBool("IsCache")
+
+	resp := new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+	status := true
+	chartInfo, err := data_manage.GetChartInfoViewByUniqueCode(uniqueCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			status = false
+		} else {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+	if chartInfo == nil {
+		status = false
+	}
+	if !status {
+		endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+		resp.EdbInfoList = endInfoList
+		resp.ChartInfo = chartInfo
+		resp.Status = false
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if d, e := utils.Rc.RedisBytes(key); e == nil {
+				err := json.Unmarshal(d, &resp)
+				if err == nil && resp != nil {
+					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+					var myCond string
+					var myPars []interface{}
+					myCond += ` AND a.admin_id=? `
+					myPars = append(myPars, adminId)
+					myCond += ` AND a.chart_info_id=? `
+					myPars = append(myPars, chartInfo.ChartInfoId)
+					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
+					if err != nil && err.Error() != utils.ErrNoRow() {
+						br.Msg = "获取失败"
+						br.ErrMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+						return
+					}
+					resp.ChartInfo.IsAdd = false
+					resp.ChartInfo.MyChartId = 0
+					resp.ChartInfo.MyChartClassifyId = ""
+					if myList != nil && len(myList) > 0 {
+						resp.ChartInfo.IsAdd = true
+						resp.ChartInfo.MyChartId = myList[0].MyChartId
+						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
+					}
+
+					br.Ret = 200
+					br.Success = true
+					br.Msg = "获取成功"
+					br.Data = resp
+					return
+				}
+			}
+		}
+	}
+	resp, isOk, msg, errMsg := GetChartInfoDetailFromUniqueCode(chartInfo, isCache, sysUser)
+	if !isOk {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// GetChartInfoDetailFromUniqueCode 根据编码获取图表详情
+func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool, sysUser *system.Admin) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
+	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+
+	adminId := sysUser.AdminId
+
+	// 指标数据map
+	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
+	defer func() {
+		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
+			resp.ChartInfo.HaveOperaAuth = true
+
+			// 指标权限
+			{
+				classifyMap := make(map[int]*data_manage.EdbClassify)
+				// 分类
+				{
+					classifyIdList := make([]int, 0)
+					for _, v := range resp.EdbInfoList {
+						classifyIdList = append(classifyIdList, v.ClassifyId)
+					}
+					classifyList, tmpErr := data_manage.GetEdbClassifyByIdList(classifyIdList)
+					if tmpErr != nil {
+						errMsg = "获取分类列表失败,Err:" + tmpErr.Error()
+						return
+					}
+					for _, v := range classifyList {
+						classifyMap[v.ClassifyId] = v
+					}
+				}
+
+				// 指标
+				if len(edbClassifyPermissionMap) < 0 {
+					edbInfoIdList := make([]int, 0)
+					for _, v := range resp.EdbInfoList {
+						edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+					}
+					edbInfoList, tmpErr := data_manage.GetEdbInfoByIdList(edbInfoIdList)
+					if tmpErr != nil {
+						errMsg = "获取指标列表失败,Err:" + tmpErr.Error()
+						return
+					}
+					for _, v := range edbInfoList {
+						edbClassifyPermissionMap[v.EdbInfoId] = data_manage_permission.EdbClassifyPermission{
+							ClassifyId:       v.ClassifyId,
+							IsJoinPermission: v.IsJoinPermission,
+							EdbInfoId:        v.EdbInfoId,
+						}
+					}
+				}
+
+				// 获取所有有权限的指标和分类
+				permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserEdbAndClassifyPermissionList(sysUser.AdminId, 0, 0)
+				if err != nil {
+					errMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+					return
+				}
+
+				for _, v := range resp.EdbInfoList {
+					// 数据权限
+					edbItem, ok := edbClassifyPermissionMap[v.EdbInfoId]
+					if !ok {
+						continue
+					}
+
+					if currClassify, ok := classifyMap[edbItem.ClassifyId]; ok {
+						v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(edbItem.IsJoinPermission, currClassify.IsJoinPermission, edbItem.EdbInfoId, edbItem.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
+					}
+				}
+			}
+		}
+	}()
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
+				err := json.Unmarshal(chartData, &resp)
+				if err != nil || resp == nil {
+					return
+				}
+				isOk = true
+				fmt.Println("source redis")
+				return
+			}
+		}
+	}
+
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
+	chartInfoId := chartInfo.ChartInfoId
+
+	// 获取指标信息
+	//chartInfo.CorrelationLeadUnit = req.LeadUnit
+	edbInfoMappingList, err := data_manage.GetChartEdbMappingList(chartInfoId)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	dateType := chartInfo.DateType
+	// 开始/结束日期
+	startYear := chartInfo.StartYear
+	startDate := chartInfo.StartDate
+	endDate := chartInfo.EndDate
+
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if chartInfo.ExtraConfig == `` {
+		msg = "配置信息错误"
+		return
+	}
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		msg = "配置信息错误"
+		errMsg = "图表配置信息错误,Err:" + err.Error()
+		return
+	}
+
+	// 获取图表数据
+	if len(edbInfoMappingList) == 0 {
+		msg = "获取失败"
+		errMsg = "图表没有指标,无法计算"
+		return
+	}
+
+	// 获取图表x轴y轴
+	edbList, xEdbIdValue, dataResp, e := rangeServ.GetChartDataByEdbInfoList(chartInfoId, dateType, startYear, startDate, endDate, edbInfoMappingList, &extraConfig)
+	if e != nil {
+		msg = "获取失败"
+		errMsg = "获取区间计算图表, 图表计算值失败, Err:" + e.Error()
+		return
+	}
+
+	if chartInfoId > 0 && chartInfo != nil {
+		//判断是否加入我的图库
+		{
+			var myChartCondition string
+			var myChartPars []interface{}
+			myChartCondition += ` AND a.admin_id=? `
+			myChartPars = append(myChartPars, sysUser.AdminId)
+			myChartCondition += ` AND a.chart_info_id=? `
+			myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+			myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				msg = "获取失败"
+				errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+				return
+			}
+			if myChartList != nil && len(myChartList) > 0 {
+				chartInfo.IsAdd = true
+				chartInfo.MyChartId = myChartList[0].MyChartId
+				chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+			}
+		}
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+	chartInfo.UnitEn = ""
+
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    chartInfo.IsEdit,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.DataResp = dataResp
+	resp.Status = true
+
+	// 遍历到校验map
+	for _, v := range edbList {
+		edbClassifyPermissionMap[v.EdbInfoId] = data_manage_permission.EdbClassifyPermission{
+			ClassifyId:       v.ClassifyId,
+			IsJoinPermission: v.IsJoinPermission,
+			EdbInfoId:        v.EdbInfoId,
+		}
+	}
+
+	// 将数据加入缓存
+	if utils.Re == nil {
+		d, _ := json.Marshal(resp)
+		_ = utils.Rc.Put(key, d, 2*time.Hour)
+	}
+	isOk = true
+	return
+}
+
+// List
+// @Title 区间计算图表列表接口
+// @Description 区间计算图表列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ChartClassifyId   query   int  true       "分类id"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "图表类型,3:区间计算,4:滚动区间计算"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /chart_info/list [get]
+func (this *RangeChartChartInfoController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	chartClassifyId, _ := this.GetInt("ChartClassifyId")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("KeyWord")
+
+	var total int
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	source := utils.CHART_SOURCE_RANGE_ANALYSIS
+
+	var condition string
+	var pars []interface{}
+
+	// 普通图表
+	condition += ` AND source = ? `
+	pars = append(pars, source)
+
+	if chartClassifyId > 0 {
+		chartClassifyId, err := data_manage.GetChartClassify(chartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取图表信息失败"
+			br.ErrMsg = "获取信息失败,GetChartClassify,Err:" + err.Error()
+			return
+		}
+		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
+	}
+	if keyword != "" {
+		//将关键词按照空格分割
+		keywords := strings.Split(keyword, " ")
+		condition += ` AND  ( chart_name LIKE '%` + keywords[0] + `%' `
+		for k, key := range keywords {
+			if k == 0 {
+				continue
+			}
+			condition += ` OR chart_name LIKE '%` + key + `%' `
+		}
+		condition += ` )`
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		condition += ` AND sys_user_id = ? `
+		pars = append(pars, sysUser.AdminId)
+	}
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		obj := data_manage.EdbInfoNoPermissionAdmin{}
+		confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range confList {
+			noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+		}
+	}
+
+	lenNoPermissionChartIdList := len(noPermissionChartIdList)
+	if lenNoPermissionChartIdList > 0 {
+		condition += ` AND chart_info_id not in (` + utils.GetOrmInReplace(lenNoPermissionChartIdList) + `) `
+		pars = append(pars, noPermissionChartIdList)
+	}
+
+	//获取图表信息
+	list, err := data_manage.GetChartListByCondition(condition, pars, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	myChartList, err := data_manage.GetMyChartListByAdminId(sysUser.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取我的图表信息失败,Err:" + err.Error()
+		return
+	}
+	myChartMap := make(map[int]*data_manage.MyChartView)
+	for _, v := range myChartList {
+		myChartMap[v.ChartInfoId] = v
+	}
+	listLen := len(list)
+	chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+	if listLen > 0 {
+		chartInfoIds := ""
+		for _, v := range list {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+	}
+	for i := 0; i < listLen; i++ {
+		//判断是否需要展示英文标识
+		if edbTmpList, ok := chartEdbMap[list[i].ChartInfoId]; ok {
+			list[i].IsEnChart = data.CheckIsEnChart(list[i].ChartNameEn, edbTmpList, list[i].Source, list[i].ChartType)
+		}
+
+		if existItem, ok := myChartMap[list[i].ChartInfoId]; ok {
+			list[i].IsAdd = true
+			list[i].MyChartId = existItem.MyChartId
+			list[i].MyChartClassifyId = existItem.MyChartClassifyId
+		}
+	}
+
+	resp := new(data_manage.ChartListResp)
+	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
+		items := make([]*data_manage.ChartInfoView, 0)
+		resp.Paging = page
+		resp.List = items
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	dataCount, err := data_manage.GetChartListCountByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, dataCount)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Copy
+// @Title 复制并新增图表接口
+// @Description 新增图表接口
+// @Params	request	body data_manage.CopyAddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/copy [post]
+func (this *RangeChartChartInfoController) Copy() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.CopyAddChartInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartInfoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartInfoId: %d", req.ChartInfoId)
+		return
+	}
+	req.ChartName = strings.TrimSpace(req.ChartName)
+	if req.ChartName == "" {
+		br.Msg = "请输入图表名称"
+		return
+	}
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "请选择图表分类"
+		return
+	}
+
+	deleteCache := true
+	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+
+	chartSource := utils.CHART_SOURCE_RANGE_ANALYSIS
+	// 校验分类、图表名称
+	{
+		var cond string
+		var pars []interface{}
+		switch this.Lang {
+		case utils.EnLangVersion:
+			cond += " AND chart_name_en = ? AND source = ? "
+		default:
+			cond += " AND chart_name = ? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, chartSource)
+		count, e := data_manage.GetChartInfoCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取同名图表失败, Err: %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "图表名称已存在, 请重新填写"
+			return
+		}
+
+		_, e = data_manage.GetChartClassifyById(req.ChartClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", e)
+			return
+		}
+	}
+
+	// 图表信息
+	originChart, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "原图表不存在"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("获取原图表信息失败, Err: %v", e)
+		return
+	}
+	if originChart.Source == utils.CHART_SOURCE_ROLLING_CORRELATION {
+		br.Msg = `滚动区间计算图不支持另存为`
+		br.IsSendEmail = false
+		return
+	}
+
+	// 普通区间计算图表
+	chartInfo := new(data_manage.ChartInfo)
+
+	newChart, err, errMsg, isSendEmail := rangeServ.CopyChartInfo(req.ChartClassifyId, req.ChartName, originChart, sysUser, this.Lang)
+	chartInfo = newChart
+
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartInfo.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "复制区间计算图表"
+		chartLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = data_manage.AddChartInfoResp{
+		ChartInfoId: chartInfo.ChartInfoId,
+		UniqueCode:  chartInfo.UniqueCode,
+		ChartType:   chartInfo.ChartType,
+	}
+	br.IsAddLog = true
+}
+
+// Refresh
+// @Title 图表刷新接口
+// @Description 图表刷新接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   UniqueCode   query   string  true       "唯一code"
+// @Success Ret=200 刷新成功
+// @router /chart_info/refresh [get]
+func (this *RangeChartChartInfoController) Refresh() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	uniqueCode := this.GetString("UniqueCode")
+	if chartInfoId <= 0 && uniqueCode == `` {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误:chartInfoId:" + strconv.Itoa(chartInfoId) + ",UniqueCode:" + uniqueCode
+		return
+	}
+
+	var chartInfo *data_manage.ChartInfo
+	var err error
+	if chartInfoId > 0 {
+		chartInfo, err = data_manage.GetChartInfoById(chartInfoId)
+	} else {
+		chartInfo, err = data_manage.GetChartInfoByUniqueCode(uniqueCode)
+	}
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,无需刷新"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		br.Msg = "刷新失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 刷新区间计算图表
+	isAsync, e := rangeServ.ChartInfoRefresh(chartInfo.ChartInfoId, chartInfo.UniqueCode)
+	if e != nil {
+		br.Msg = "刷新失败"
+		br.ErrMsg = "刷新区间计算图表失败, Err:" + err.Error()
+		return
+	}
+	// 多因子区间计算异步刷新, 前端提示
+	if isAsync {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "刷新时间较长, 请10分钟后查看"
+		return
+	}
+
+	//清除图表缓存
+	{
+		key := utils.HZ_CHART_LIB_DETAIL + chartInfo.UniqueCode
+		_ = utils.Rc.Delete(key)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "刷新成功"
+}
+
+// MultipleGraphConfigSaveEdb
+// @Title 保存指标接口
+// @Description 保存指标接口
+// @Param	request	body request.SaveMultipleGraphEdbReq true "type json string"
+// @Success Ret=200 返回指标id
+// @router /edb/save [post]
+func (this *RangeChartChartInfoController) MultipleGraphConfigSaveEdb() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req data_manage.SaveChartRangeAnalysisEdbReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	cacheKey := "CACHE_CHART_EDB_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+	edbInfoIds := make([]int, 0)
+	for _, v := range req.EdbInfoList {
+		if v.EdbInfoId > 0 {
+			edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+		}
+		edbInfoIds = append(edbInfoIds, v.FromEdbInfoId)
+	}
+	edbInfoMappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIds)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoMap := make(map[int]*data_manage.ChartEdbInfoMapping, 0)
+	for _, v := range edbInfoMappingList {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+
+	for _, v := range req.EdbInfoList {
+		if _, ok := edbInfoMap[v.FromEdbInfoId]; !ok {
+			br.Msg = "指标信息不存在"
+			br.ErrMsg = "指标信息不存在,EdbInfoId:" + strconv.Itoa(v.FromEdbInfoId)
+			br.IsSendEmail = false
+			return
+		}
+		if v.EdbInfoId > 0 {
+			if _, ok := edbInfoMap[v.EdbInfoId]; !ok {
+				v.EdbInfoId = 0
+			}
+		}
+	}
+	// 区间计算图表配置校验
+	extraConfig := req.ExtraConfig
+	// 区间计算图表配置校验
+	var config data_manage.ChartRangeAnalysisExtraConf
+	if req.ExtraConfig == `` {
+		br.Msg = "请输入配置信息"
+		return
+	}
+	err = json.Unmarshal([]byte(req.ExtraConfig), &config)
+	if err != nil {
+		br.Msg = "配置信息格式错误"
+		br.ErrMsg = "配置信息格式错误,Err:" + err.Error()
+		return
+	}
+
+	err, errMsg, isSendEmail := rangeServ.CheckChartRangeExtraConfig(config)
+	if err != nil {
+		br.Msg = "配置信息校验失败"
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	multipleGraphConfigEdbMappingList, err := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "查询配置与图表的关联关系失败,ERR:" + err.Error()
+		return
+	}
+	deleteEdbInfoIds := make([]int, 0) // 需要解除绑定的指标ID
+	deleteEdbInfoIdMap := make(map[int]int, 0)
+	configMapping := make(map[int]*data_manage.MultipleGraphConfigEdbMapping, 0)
+	if len(multipleGraphConfigEdbMappingList) == 0 || req.IsSaveAs {
+		// 需要新增的指标
+	} else {
+		// 需要更新的指标
+		// 查询原先所有指标的来源指标,进一步筛选出需要新增,或者更新 或者删除的指标
+		for _, v := range multipleGraphConfigEdbMappingList {
+			deleteEdbInfoIdMap[v.EdbInfoId] = v.EdbInfoId
+			configMapping[v.EdbInfoId] = v
+		}
+	}
+	resp := data_manage.BatchEdbInfoCalculateBatchSaveResp{
+		Fail:    make([]data_manage.BatchEdbInfoCalculateBatchSaveFailResp, 0),
+		Success: make([]data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp, 0),
+	}
+	// 普通指标批量新增\批量编辑
+	var saveReq data_manage.BatchEdbInfoCalculateBatchSaveReq
+	calculateEdbList := make([]data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp, 0)
+	addReqEdbList := make([]*data_manage.CalculateEdbInfoItem, 0)
+	editReqEdbList := make([]*data_manage.CalculateEdbInfoItem, 0)
+	for _, v := range req.EdbInfoList {
+
+		v.CalculateId = strconv.Itoa(v.FromEdbInfoId)
+		v.EdbName = strings.Trim(v.EdbName, " ")
+		if v.EdbName == "" {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "指标名称不能为空",
+			})
+			continue
+		}
+
+		if v.Frequency == "" {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "频率不能为空",
+			})
+			continue
+		}
+
+		if v.Unit == "" {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "单位不能为空",
+			})
+			continue
+		}
+
+		if v.ClassifyId <= 0 {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "请选择分类",
+			})
+			continue
+		}
+
+		//加入缓存机制,避免创建同一个名称的指标 start
+		redisKey := fmt.Sprint("edb_info:calculate:batch:save:", utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS, ":", v.EdbName)
+		//加入缓存机制,避免创建同一个名称的指标 start
+		if req.EdbInfoType == 1 {
+			redisKey = fmt.Sprint("predict_edb_info:calculate:batch:save:", sysUser.AdminId, ":", utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS, ":", v.CalculateId)
+		}
+		isExist := utils.Rc.IsExist(redisKey)
+		if isExist {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: strconv.Itoa(v.FromEdbInfoId),
+				Msg:         "指标正在处理,请勿重复提交",
+			})
+			continue
+		} else {
+		}
+		if v.EdbInfoId > 0 {
+			editReqEdbList = append(editReqEdbList, v)
+			delete(deleteEdbInfoIdMap, v.EdbInfoId)
+		} else {
+			addReqEdbList = append(addReqEdbList, v)
+		}
+	}
+	if len(editReqEdbList) <= 0 && len(addReqEdbList) <= 0 {
+		br.Msg = "新增失败!"
+		if len(resp.Fail) > 0 {
+			br.ErrMsg = resp.Fail[0].Msg
+		} else {
+			br.Msg = "请选择指标"
+		}
+		return
+	}
+	if req.EdbInfoType == 0 { //普通指标
+		saveReq.Source = utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS
+		saveReq.CalculateFormula = extraConfig
+		saveReq.AdminId = sysUser.AdminId
+		saveReq.AdminName = sysUser.RealName
+		if len(addReqEdbList) > 0 {
+			saveReq.EdbList = addReqEdbList
+			// 调用指标库去更新
+			reqJson, err := json.Marshal(saveReq)
+			if err != nil {
+				br.Msg = "参数解析异常!"
+				br.ErrMsg = "参数解析失败,Err:" + err.Error()
+				return
+			}
+			respItem, err := data.BatchSaveEdbCalculateMultiData(string(reqJson), this.Lang)
+			if err != nil {
+				br.Msg = "新增失败!"
+				br.ErrMsg = "新增失败,Err:" + err.Error()
+				return
+			}
+			if respItem.Ret != 200 {
+				br.Msg = respItem.Msg
+				br.ErrMsg = respItem.ErrMsg
+				return
+			}
+			resp = respItem.Data
+			calculateEdbList = respItem.Data.Success
+		}
+
+		if len(editReqEdbList) > 0 {
+			saveReq.EdbList = editReqEdbList
+			// 调用指标库去更新
+			reqJson, err := json.Marshal(saveReq)
+			if err != nil {
+				br.Msg = "参数解析异常!"
+				br.ErrMsg = "参数解析失败,Err:" + err.Error()
+				return
+			}
+			respItem, err := data.BatchEditEdbCalculateMultiData(string(reqJson), this.Lang)
+			if err != nil {
+				br.Msg = "更新失败!"
+				br.ErrMsg = "更新失败,Err:" + err.Error()
+				return
+			}
+			if respItem.Ret != 200 {
+				br.Msg = respItem.Msg
+				br.ErrMsg = respItem.ErrMsg
+				return
+			}
+			resp.Fail = append(resp.Fail, respItem.Data.Fail...)
+			calculateEdbList = append(calculateEdbList, respItem.Data.Success...)
+		}
+	} else if req.EdbInfoType == 1 {
+		// 预测指标
+		for _, v := range req.EdbInfoList {
+			predictReq := new(data_manage.PredictEdbInfoCalculateBatchSaveReq)
+			predictReq.EdbInfoId = v.EdbInfoId
+			predictReq.CalculateFormula = extraConfig
+			predictReq.EdbName = v.EdbName
+			predictReq.Frequency = v.Frequency
+			predictReq.Unit = v.Unit
+			predictReq.ClassifyId = v.ClassifyId
+			predictReq.FromEdbInfoId = v.FromEdbInfoId
+			predictReq.AdminId = sysUser.AdminId
+			predictReq.AdminName = sysUser.RealName
+			predictReq.Source = utils.DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS
+			reqJson, e := json.Marshal(predictReq)
+			if e != nil {
+				br.Msg = "参数解析异常!"
+				br.ErrMsg = "参数解析失败,Err:" + e.Error()
+				return
+			}
+			respItem, e := data.BatchSavePredictEdbData(string(reqJson), this.Lang)
+			if e != nil {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         "新增失败!",
+					ErrMsg:      "新增失败,Err:" + e.Error(),
+				})
+				continue
+			}
+			if respItem.Ret != 200 {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         respItem.Msg,
+					ErrMsg:      respItem.ErrMsg,
+				})
+				continue
+			}
+
+			calculateEdbList = append(calculateEdbList, data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp{
+				CalculateId: v.CalculateId,
+				EdbInfoId:   respItem.Data.EdbInfoId,
+				UniqueCode:  respItem.Data.UniqueCode,
+				ClassifyId:  predictReq.ClassifyId,
+			})
+		}
+	}
+
+	// 批量删除
+	for k, _ := range deleteEdbInfoIdMap {
+		deleteEdbInfoIds = append(deleteEdbInfoIds, k)
+	}
+	if len(deleteEdbInfoIds) > 0 {
+		err = data_manage.DeleteMultipleGraphConfigEdbMappingByEdbIds(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS, deleteEdbInfoIds)
+		if err != nil {
+			br.Msg = "更新失败!"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	// 如果不是另存为的话,那么需要建立配置与指标的关系
+	if !req.IsSaveAs {
+		// 如果指标没有建立关联关系,那么就需要添加关系
+		configEdbMappingList := make([]*data_manage.MultipleGraphConfigEdbMapping, 0)
+		for _, v := range calculateEdbList {
+			item, ok := configMapping[v.EdbInfoId]
+			if !ok {
+				multipleGraphConfigEdbMapping := &data_manage.MultipleGraphConfigEdbMapping{
+					//Id:                    0,
+					MultipleGraphConfigId: req.MultipleGraphConfigId,
+					EdbInfoId:             v.EdbInfoId,
+					Source:                utils.CHART_SOURCE_RANGE_ANALYSIS,
+					ModifyTime:            time.Now(),
+					CreateTime:            time.Now(),
+				}
+				configEdbMappingList = append(configEdbMappingList, multipleGraphConfigEdbMapping)
+			} else {
+				item.ModifyTime = time.Now()
+				err = item.Update([]string{"ModifyTime"})
+				if err != nil {
+					br.Msg = "保存失败"
+					br.ErrMsg = "保存配置与指标的关联关系失败,ERR:" + err.Error()
+					return
+				}
+			}
+		}
+		if len(configEdbMappingList) > 0 {
+			err = data_manage.AddMultipleGraphConfigEdbMappingList(configEdbMappingList)
+			if err != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = "保存配置与指标的关联关系失败,ERR:" + err.Error()
+				return
+			}
+		}
+	}
+	resp.Success = calculateEdbList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// MultipleGraphConfigEdbList
+// @Title 获取指标列表接口
+// @Description 获取指标列表接口
+// @Param	request	body request.SaveMultipleGraphEdbReq true "type json string"
+// @Success Ret=200 返回指标id
+// @router /edb/list [get]
+func (this *RangeChartChartInfoController) MultipleGraphConfigEdbList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	multipleGraphConfigId, _ := this.GetInt("MultipleGraphConfigId")
+	if multipleGraphConfigId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,MultipleGraphConfigId Is Empty"
+		return
+	}
+
+	list := make([]*data_manage.ChartRangeAnalysisConfigEdbItem, 0)
+	resp := data_manage.ChartRangeAnalysisConfigEdbResp{EdbInfoList: list}
+
+	multipleGraphConfigEdbMappingList, err := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(multipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "查询配置与图表的关联关系失败,ERR:" + err.Error()
+		return
+	}
+	if len(multipleGraphConfigEdbMappingList) == 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		br.Data = resp
+		br.IsAddLog = true
+		return
+	}
+
+	edbInfoIds := make([]int, 0)
+	for _, v := range multipleGraphConfigEdbMappingList {
+		edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+	}
+	edbList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIds)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoMap := make(map[int]*data_manage.ChartEdbInfoMapping, 0)
+	for _, v := range edbList {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+
+	edbCalculateMappingList, e := data_manage.GetEdbInfoCalculateMappingListByEdbInfoIds(edbInfoIds)
+	if e != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "获取计算指标信息失败,ERR:" + e.Error()
+		return
+	}
+
+	// 说明指标还在,没有被删除
+	for _, v := range edbCalculateMappingList {
+		tmp := new(data_manage.ChartRangeAnalysisConfigEdbItem)
+		info, ok := edbInfoMap[v.EdbInfoId]
+		if ok {
+			tmp.EdbInfoId = v.EdbInfoId
+			tmp.Unit = info.Unit
+			tmp.UnitEn = info.UnitEn
+			tmp.Frequency = info.Frequency
+			tmp.EdbNameEn = info.EdbNameEn
+			tmp.EdbName = info.EdbName
+			tmp.ClassifyId = info.ClassifyId
+			tmp.FromEdbInfoId = v.FromEdbInfoId
+			tmp.EdbTypeInfo = info.EdbInfoCategoryType
+			list = append(list, tmp)
+		}
+	}
+	resp.EdbInfoList = list
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// SearchByEs
+// @Title 图表模糊搜索(从es获取)
+// @Description  图表模糊搜索(从es获取)
+// @Param   Keyword   query   string  true       "图表名称"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "来源,3:相关性,4:滚动相关性,默认0:全部"
+// @Success 200 {object} data_manage.ChartInfo
+// @router /chart_info/search_by_es [get]
+func (this *RangeChartChartInfoController) SearchByEs() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := this.GetString("Keyword")
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	sourceList := make([]int, 0)
+	sourceList = append(sourceList, utils.CHART_SOURCE_RANGE_ANALYSIS)
+
+	var searchList []*data_manage.ChartInfo
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		obj := data_manage.EdbInfoNoPermissionAdmin{}
+		confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range confList {
+			noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+		}
+	}
+
+	if keyword != "" {
+		searchList, total, err = data.EsSearchChartInfo(keyword, showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+	} else {
+		total, searchList, err = data_manage.ChartInfoSearchByEmptyKeyWord(showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	finalList := make([]*data_manage.ChartInfoMore, 0)
+	if len(searchList) > 0 {
+		chartInfoIds := ""
+		chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+		for _, v := range searchList {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+
+		for _, v := range searchList {
+			tmp := new(data_manage.ChartInfoMore)
+			tmp.ChartInfo = *v
+			// 图表数据权限
+			tmp.HaveOperaAuth = true
+			//判断是否需要展示英文标识
+			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
+			}
+			finalList = append(finalList, tmp)
+		}
+	}
+	//新增搜索词记录
+	{
+		searchKeyword := new(data_manage.SearchKeyword)
+		searchKeyword.KeyWord = keyword
+		searchKeyword.CreateTime = time.Now()
+		go data_manage.AddSearchKeyword(searchKeyword)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
+	resp := data_manage.ChartInfoListByEsResp{
+		Paging: page,
+		List:   finalList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// BaseInfoEdit
+// @Title 编辑图表基础信息接口
+// @Description 编辑图表基础信息接口
+// @Param	request	body data_manage.EditChartInfoBaseReq true "type json string"
+// @Success Ret=200 编辑成功
+// @router /chart_info/base/edit [post]
+func (this *RangeChartChartInfoController) BaseInfoEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.EditChartRangeBaseReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartInfoId <= 0 {
+		br.Msg = "请选择图表"
+		return
+	}
+	if req.ChartName == "" {
+		br.Msg = "请输入图表名称"
+		return
+	}
+
+	chartItem, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取图表信息失败, Err: " + e.Error()
+		return
+	}
+	if chartItem.Source != utils.CHART_SOURCE_RANGE_ANALYSIS {
+		br.Msg = "该图不是区间计算图表"
+		return
+	}
+
+	var condition string
+	var pars []interface{}
+
+	condition += " AND chart_info_id <> ? AND source = ? "
+	pars = append(pars, req.ChartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+
+	switch this.Lang {
+	case utils.EnLangVersion:
+		condition += " AND chart_name_en = ? "
+	default:
+		condition += " AND chart_name = ? "
+	}
+	pars = append(pars, req.ChartName)
+	existItem, err := data_manage.GetChartInfoByCondition(condition, pars)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			br.Msg = "判断英文图表名称是否存在失败"
+			br.ErrMsg = "判断英文图表名称是否存在失败,Err:" + err.Error()
+			return
+		}
+	}
+	if err == nil && existItem.ChartInfoId > 0 {
+		br.Msg = existItem.ChartName + ":" + req.ChartName + "图表名称已存在"
+		return
+	}
+
+	switch this.Lang {
+	case utils.EnLangVersion:
+		chartItem.ChartNameEn = req.ChartName
+	default:
+		chartItem.ChartName = req.ChartName
+	}
+
+	chartItem.ModifyTime = time.Now().Local()
+	if e := chartItem.Update([]string{"ChartName", "ChartNameEn", "ModifyTime"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新图表信息失败, Err: " + e.Error()
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑区间计算图表基础信息"
+		chartLog.Method = this.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	// 清除缓存
+	if utils.Re == nil && utils.Rc != nil {
+		_ = utils.Rc.Delete(utils.HZ_CHART_LIB_DETAIL + chartItem.UniqueCode) //图表分享链接缓存
+		_ = utils.Rc.Delete(data.GetChartInfoDataKey(req.ChartInfoId))
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+	br.IsAddLog = true
+}
+
+// @Title 保存图表接口
+// @Description 保存图表接口
+// @Param	request	body data_manage.SaveChartInfoReq true "type json string"
+// @Success Ret=200 返回图表id
+// @router /chart_info/save [post]
+func (this *RangeChartChartInfoController) ChartInfoSave() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req data_manage.SaveChartRangeReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartInfoId <= 0 {
+		br.Msg = "参数错误!"
+		return
+	}
+
+	chartItem, err := data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,请刷新页面!"
+			br.ErrMsg = "图表已被删除,请刷新页面,ChartInfoId:" + strconv.Itoa(req.ChartInfoId)
+			return
+		}
+		br.Msg = "获取图表信息失败!"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	//图表操作权限
+	//ok := data.CheckOpChartPermission(sysUser, chartItem.SysUserId)
+	//if !ok {
+	//	br.Msg = "没有该图表的操作权限"
+	//	br.ErrMsg = "没有该图表的操作权限"
+	//	return
+	//}
+
+	err = data_manage.EditRangeChartInfo(&req)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	key := utils.HZ_CHART_LIB_DETAIL + chartItem.UniqueCode
+	if utils.Re == nil && utils.Rc != nil {
+		utils.Rc.Delete(key)
+	}
+
+	//修改es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "修改配置项"
+		chartLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.IsAddLog = true
+}

+ 23 - 0
controllers/report_v2.go

@@ -14,6 +14,8 @@ import (
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"html"
+	"io"
+	"os"
 	"strconv"
 	"strings"
 	"time"
@@ -1954,3 +1956,24 @@ func fixSmartReport() {
 
 	fmt.Println("修复智能研报完成")
 }
+func initPdf() {
+	inFile := "anNNgk3Bbi4LRULwcJgNOPrREYh5.pdf"
+	f2, err := services.GeneralWaterMarkPdf(inFile, "颜鹏 - 18170239278")
+	//f2, err := services.GeneralWaterMarkPdf(inFile, "上周美国馏分油库存累库95万桶,馏分油表需环比下降(-25.6万桶/日)。本期馏分油产量继续抬升,在供增需减的环比变动下库存持续累库。馏分油供应的增加我们认为可能和进口的油种有关,今年以来美国进口的中重质原油占比不断走高,尤其是5")
+	if err != nil {
+		fmt.Println("生成失败,ERR:", err)
+		return
+	}
+
+	// 创建一个新的文件
+	newPdf, err := os.Create("new0555.pdf")
+	if err != nil {
+		fmt.Println("创建临时文件失败,Err:", err)
+		return
+	}
+	defer func() {
+		_ = newPdf.Close()
+	}()
+
+	_, _ = io.Copy(newPdf, f2)
+}

+ 5 - 0
go.mod

@@ -88,6 +88,8 @@ require (
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/hhrutter/lzw v1.0.0 // indirect
+	github.com/hhrutter/tiff v1.0.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -96,6 +98,7 @@ require (
 	github.com/leodido/go-urn v1.2.0 // indirect
 	github.com/magiconair/properties v1.8.1 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mattn/go-runewidth v0.0.15 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
 	github.com/minio/sha256-simd v1.0.1 // indirect
@@ -105,6 +108,7 @@ require (
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
+	github.com/pdfcpu/pdfcpu v0.8.0 // indirect
 	github.com/pelletier/go-toml v1.9.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.16.0 // indirect
@@ -113,6 +117,7 @@ require (
 	github.com/prometheus/procfs v0.10.1 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/rs/xid v1.5.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect

+ 11 - 0
go.sum

@@ -319,6 +319,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
+github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
+github.com/hhrutter/tiff v1.0.1 h1:MIus8caHU5U6823gx7C6jrfoEvfSTGtEFRiM8/LOzC0=
+github.com/hhrutter/tiff v1.0.1/go.mod h1:zU/dNgDm0cMIa8y8YwcYBeuEEveI4B0owqHyiPpJPHc=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
@@ -376,6 +380,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -443,6 +449,8 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:Ff
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pdfcpu/pdfcpu v0.8.0 h1:SuEB4uVsPFz1nb802r38YpFpj9TtZh/oB0bGG34IRZw=
+github.com/pdfcpu/pdfcpu v0.8.0/go.mod h1:jj03y/KKrwigt5xCi8t7px2mATcKuOzkIOoCX62yMho=
 github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.9.2 h1:7NiByeVF4jKSG1lDF3X8LTIkq2/bu+1uYbIm1eS5tzk=
@@ -496,6 +504,9 @@ github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK
 github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
 github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=

+ 438 - 0
models/data_manage/chart_info_range_analysis.go

@@ -0,0 +1,438 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type ChartRangeAnalysisExtraConf struct {
+	EdbInfoMode           int                                `description:"指标模式 0: 单指标,1: 多指标"`
+	DateRangeType         int                                `description:"区间划分类型 0:智能划分,1:手工划分,2:跨年划分"`
+	AutoDateConf          ChartRangeAnalysisAutoDateConf     `description:"智能划分时间区间配置"`
+	ManualDateConf        []ChartRangeAnalysisManualDateConf `description:"手工划分时间区间配置"`
+	YearDateConf          ChartRangeAnalysisYearDateConf     `description:"跨年划分时间区间配置"`
+	CalculateType         int                                `description:"计算类型 0: 区间均值,1: 区间累计值,2:区间涨幅,3:区间年化增长率,4:区间最大值,5:区间最小值"`
+	UnNormalDataDealType  int                                `description:"异常值处理配置 0:不处理,1:剔除,2替换"`
+	UnNormalDataConf      ChartRangeAnalysisDeleteDataConf
+	DataConvertType       int                               `description:"数据转换类型 0不转, 1乘 2除 3对数"`
+	DataConvertConf       ChartRangeAnalysisDataConvertConf `description:"数据转换详情"`
+	SeriesName            string                            `description:"指标系列名称"`
+	EdbInfoType           int                               `description:"指标类型:0普通指标,1预测指标"`
+	MultipleGraphConfigId int                               `description:"配置ID"`
+}
+
+type ChartRangeAnalysisAutoDateChangeConf struct {
+	BaseDateType int `description:"基准日期类型:0指标日期,1系统日期"`
+	MoveForward  int `description:"前移的期数"`
+	DateChange   []*EdbDataDateChangeConf
+}
+
+type EdbDataDateChangeConf struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
+type ChartRangeAnalysisDeleteDataConf struct {
+	Formula      string  `description:"比较符号:=、>、<、>=、<="`
+	Value        float64 `description:"比较的值"`
+	ReplaceValue float64 `description:"替换的值"`
+}
+
+type ChartRangeAnalysisDataConvertConf struct {
+	Value  float64 `description:"数据转换值"`
+	Unit   string  `description:"数据转换单位"`
+	EnUnit string  `description:"数据转换单位"`
+}
+
+type ChartRangeAnalysisManualDateConf struct { //手工划分
+	StartDate string `description:"开始日期"`
+	EndDate   string `description:"结束日期"`
+}
+
+type ChartRangeAnalysisAutoDateConf struct { //智能划分
+	IsAutoStartDate int                                  `description:"起始日期是否是动态设置:0固定,1动态"`
+	StartDate       string                               `description:"固定模式下的起始日期"`
+	EndDate         string                               `description:"固定模式下的截止日期"`
+	IsAutoEndDate   int                                  `description:"截止日期是否是动态设置:0固定,1动态"`
+	StartDateConf   ChartRangeAnalysisAutoDateChangeConf `description:"动态起始日期配置"`
+	EndDateConf     ChartRangeAnalysisAutoDateChangeConf `description:"动态截止日期配置"`
+}
+
+type ChartRangeAnalysisYearDateConf struct {
+	StartDay string `description:"开始日"`
+	EndDay   string `description:"结束日"`
+}
+
+// ChartRangeAnalysisPreviewReq 相关性图表请求体
+type ChartRangeAnalysisPreviewReq struct {
+	ExtraConfig      ChartRangeAnalysisExtraConf
+	ChartEdbInfoList []*ChartSaveItem `description:"指标及配置信息"`
+	DateType         int              `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间,6:起始日期至今 20:最近N年"`
+	StartDate        string           `description:"自定义开始日期"`
+	EndDate          string           `description:"自定义结束日期"`
+	StartYear        int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+}
+
+type ChartRangeAnalysisManualDateConfList []ChartRangeAnalysisManualDateConf
+
+func (a ChartRangeAnalysisManualDateConfList) Len() int      { return len(a) }
+func (a ChartRangeAnalysisManualDateConfList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ChartRangeAnalysisManualDateConfList) Less(i, j int) bool {
+	return a[i].StartDate < a[j].StartDate
+}
+
+type ChartRangeAnalysisDataResp struct { //图表详情返回值
+	*ChartRangeAnalysisExtraConf
+	SeriesId     int `description:"指标系列ID"`
+	ConfigEdbNum int `description:"生成的指标数"`
+}
+
+type ChartRangeAnalysisDateDataItem struct {
+	StartDate time.Time
+	EndDate   time.Time
+	DataList  []*EdbDataList
+}
+
+// CreateRangeChartAndEdb 新增区间计算图表
+func CreateRangeChartAndEdb(chartInfo *ChartInfo, edbMappingList []*ChartEdbMapping) (chartInfoId int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+		} else {
+			_ = tx.Commit()
+		}
+	}()
+
+	// 新增图表信息
+	newId, err := tx.Insert(chartInfo)
+	if err != nil {
+		return
+	}
+	// 指标mapping
+	chartInfo.ChartInfoId = int(newId)
+	chartInfoId = int(newId)
+	if len(edbMappingList) > 0 {
+		for i := range edbMappingList {
+			edbMappingList[i].ChartInfoId = chartInfoId
+		}
+		_, err = tx.InsertMulti(len(edbMappingList), edbMappingList)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// EditRangeChartInfoAndMapping 修改区间计算图表的 图表与指标 的关系
+func EditRangeChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr, lang string, calendar string, dateType, disabled int, barChartConf string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	var pars []interface{}
+	pars = append(pars, req.ChartName)
+	pars = append(pars, req.ChartNameEn)
+	pars = append(pars, edbInfoIdStr)
+	pars = append(pars, req.ChartType)
+	pars = append(pars, req.ChartClassifyId)
+	pars = append(pars, disabled)
+	pars = append(pars, barChartConf)
+	pars = append(pars, req.ExtraConfig)
+	pars = append(pars, req.StartYear)
+	pars = append(pars, req.ChartThemeId)
+	pars = append(pars, req.SourcesFrom)
+	pars = append(pars, req.Instructions)
+	pars = append(pars, req.MarkersLines)
+	pars = append(pars, req.MarkersAreas)
+	pars = append(pars, req.Unit)
+	pars = append(pars, req.UnitEn)
+
+	sql := ` UPDATE  chart_info
+			SET
+			  chart_name =?,
+			  chart_name_en =?,
+              edb_info_ids=?,
+			  chart_type=?,
+			  chart_classify_id = ?,
+			  modify_time = NOW(),
+              disabled = ?,
+              bar_config = ?,
+              extra_config = ?, 
+ 			  start_year = ?,
+ 			  chart_theme_id = ?,
+ 			  sources_from = ?,
+ 			  instructions = ?,
+ 			  markers_lines = ?,
+ 			  markers_areas = ?,
+ 			  unit = ?,
+ 			  unit_en = ?
+			`
+	if calendar != "" {
+		sql += `,calendar = ? `
+		pars = append(pars, calendar)
+	}
+	if dateType > 0 {
+		sql += `,date_type = ? `
+		pars = append(pars, dateType)
+	}
+
+	sql += `,start_date = ? `
+	pars = append(pars, req.StartDate)
+
+	sql += `,end_date = ? `
+	pars = append(pars, req.EndDate)
+
+	sql += `,season_start_date = ? `
+	pars = append(pars, req.StartDate)
+
+	sql += `,season_end_date = ? `
+	pars = append(pars, req.EndDate)
+
+	sql += `,left_min = ? `
+	pars = append(pars, req.LeftMin)
+
+	sql += `,left_max = ? `
+	pars = append(pars, req.LeftMax)
+
+	sql += `,right_min = ? `
+	pars = append(pars, req.RightMin)
+
+	sql += `,right_max = ? `
+	pars = append(pars, req.RightMax)
+
+	sql += `,right2_min = ? `
+	pars = append(pars, req.Right2Min)
+
+	sql += `,right2_max = ? `
+	pars = append(pars, req.Right2Max)
+
+	sql += `,min_max_save = ? `
+	pars = append(pars, req.MinMaxSave)
+
+	sql += `WHERE chart_info_id = ?`
+
+	pars = append(pars, req.ChartInfoId)
+	_, err = to.Raw(sql, pars).Exec()
+	if err != nil {
+		fmt.Println("UPDATE  chart_info Err:", err.Error())
+		return err
+	}
+	chartEdbMappingIdList := make([]string, 0)
+	for _, v := range req.ChartEdbInfoList {
+		// 查询该指标是否存在,如果存在的话,那么就去修改,否则新增
+		var tmpChartEdbMapping *ChartEdbMapping
+		csql := `SELECT *  FROM chart_edb_mapping WHERE chart_info_id=? AND edb_info_id=? AND source = ? `
+		err = to.Raw(csql, req.ChartInfoId, v.EdbInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS).QueryRow(&tmpChartEdbMapping)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			fmt.Println("QueryRow Err:", err.Error())
+			return err
+		}
+		if tmpChartEdbMapping != nil {
+			chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(tmpChartEdbMapping.ChartEdbMappingId))
+			tmpChartEdbMapping.ModifyTime = time.Now()
+			//tmpChartEdbMapping.MaxData = v.MaxData
+			//tmpChartEdbMapping.MinData = v.MinData
+			//tmpChartEdbMapping.IsOrder = v.IsOrder
+			tmpChartEdbMapping.EdbAliasName = v.EdbAliasName
+			tmpChartEdbMapping.IsAxis = v.IsAxis
+			//tmpChartEdbMapping.EdbInfoType = v.EdbInfoType
+			//tmpChartEdbMapping.LeadValue = v.LeadValue
+			//tmpChartEdbMapping.LeadUnit = v.LeadUnit
+			//tmpChartEdbMapping.ChartStyle = v.ChartStyle
+			//tmpChartEdbMapping.ChartColor = v.ChartColor
+			//tmpChartEdbMapping.PredictChartColor = v.PredictChartColor
+			//tmpChartEdbMapping.ChartWidth = v.ChartWidth
+			_, err = to.Update(tmpChartEdbMapping, "ModifyTime", "IsAxis", "EdbAliasName")
+			if err != nil {
+				fmt.Println("chart_edb_mapping Err:" + err.Error())
+				return err
+			}
+		} else {
+			mapItem := new(ChartEdbMapping)
+			mapItem.ChartInfoId = req.ChartInfoId
+			mapItem.EdbInfoId = v.EdbInfoId
+			mapItem.EdbAliasName = v.EdbAliasName
+			mapItem.IsAxis = v.IsAxis
+			mapItem.CreateTime = time.Now()
+			mapItem.ModifyTime = time.Now()
+			timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+			mapItem.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp + "_" + strconv.Itoa(v.EdbInfoId))
+			//mapItem.MaxData = v.MaxData
+			//mapItem.MinData = v.MinData
+			//mapItem.IsOrder = true
+			mapItem.EdbInfoType = 1
+			//mapItem.LeadValue = v.LeadValue
+			//mapItem.LeadUnit = v.LeadUnit
+			//mapItem.ChartStyle = v.ChartStyle
+			//mapItem.ChartColor = v.ChartColor
+			//mapItem.PredictChartColor = v.PredictChartColor
+			//mapItem.ChartWidth = v.ChartWidth
+			mapItem.Source = utils.CHART_SOURCE_RANGE_ANALYSIS
+			tmpId, err := to.Insert(mapItem)
+			if err != nil {
+				fmt.Println("AddChartEdbMapping Err:" + err.Error())
+				return err
+			}
+			mapItem.ChartEdbMappingId = int(tmpId)
+			chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(mapItem.ChartEdbMappingId))
+		}
+	}
+	if len(chartEdbMappingIdList) > 0 {
+		chartEdbMappingIdStr := strings.Join(chartEdbMappingIdList, ",")
+		dsql := `DELETE FROM chart_edb_mapping WHERE chart_info_id=? AND chart_edb_mapping_id NOT IN(` + chartEdbMappingIdStr + `)`
+		_, err = to.Raw(dsql, req.ChartInfoId).Exec()
+		if err != nil {
+			fmt.Println("delete err:" + err.Error())
+			return err
+		}
+	}
+
+	return
+}
+
+// EditRangeChartInfo 修改区间计算图表基本信息
+func EditRangeChartInfo(req *SaveChartRangeReq) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	var pars []interface{}
+	pars = append(pars, req.StartYear)
+
+	sql := ` UPDATE  chart_info
+			SET
+			  modify_time = NOW(),
+ 			  start_year = ?
+			`
+	if req.DateType > 0 {
+		sql += `,date_type = ? `
+		pars = append(pars, req.DateType)
+	}
+
+	sql += `,start_date = ? `
+	pars = append(pars, req.StartDate)
+
+	sql += `,end_date = ? `
+	pars = append(pars, req.EndDate)
+
+	sql += `,left_min = ? `
+	pars = append(pars, req.LeftMin)
+
+	sql += `,left_max = ? `
+	pars = append(pars, req.LeftMax)
+
+	sql += `,right_min = ? `
+	pars = append(pars, req.RightMin)
+
+	sql += `,right_max = ? `
+	pars = append(pars, req.RightMax)
+
+	sql += `,right2_min = ? `
+	pars = append(pars, req.Right2Min)
+
+	sql += `,right2_max = ? `
+	pars = append(pars, req.Right2Max)
+
+	sql += `,min_max_save = ? `
+	pars = append(pars, req.MinMaxSave)
+
+	sql += `WHERE chart_info_id = ?`
+
+	pars = append(pars, req.ChartInfoId)
+	_, err = to.Raw(sql, pars).Exec()
+	if err != nil {
+		fmt.Println("UPDATE  chart_info Err:", err.Error())
+		return err
+	}
+
+	return
+}
+
+// SaveChartRangeAnalysisEdbReq 指标保存请求
+type SaveChartRangeAnalysisEdbReq struct {
+	EdbInfoList           []*CalculateEdbInfoItem `description:"指标列表"`
+	ExtraConfig           string
+	MultipleGraphConfigId int  `description:"配置id"`
+	IsSaveAs              bool `description:"是否另存为,true的话,就是另存为,不会建立与配置的关系"`
+	EdbInfoType           int  `description:"指标类型"`
+}
+
+// ChartRangeAnalysisConfigEdbResp 指标列表
+type ChartRangeAnalysisConfigEdbResp struct {
+	EdbInfoList []*ChartRangeAnalysisConfigEdbItem `description:"指标列表"`
+}
+
+type ChartRangeAnalysisConfigEdbItem struct {
+	EdbInfoId     int    `description:"指标id"`
+	EdbName       string `description:"指标名称"`
+	EdbNameEn     string `description:"指标名称"`
+	Frequency     string `description:"频度"`
+	Unit          string `description:"单位"`
+	UnitEn        string `description:"单位"`
+	ClassifyId    int    `description:"分类id"`
+	FromEdbInfoId int    `description:"计算来源指标id"`
+	EdbTypeInfo   int    `description:"指标类型,0普通指标,1预测指标"`
+}
+
+type SortEdbDataList []*EdbDataList
+
+func (m SortEdbDataList) Len() int {
+	return len(m)
+}
+
+func (m SortEdbDataList) Less(i, j int) bool {
+	return m[i].DataTime > m[j].DataTime
+}
+
+func (m SortEdbDataList) Swap(i, j int) {
+	m[i], m[j] = m[j], m[i]
+}
+
+type EditChartRangeBaseReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	ChartName   string `description:"英文图表名称"`
+}
+type SaveChartRangeReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	DateType    int    `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间,6:起始日期至今,20:N年至今"`
+	StartDate   string `description:"自定义开始日期"`
+	EndDate     string `description:"自定义结束日期"`
+	Calendar    string `description:"公历/农历"`
+	LeftMin     string `description:"图表左侧最小值"`
+	LeftMax     string `description:"图表左侧最大值"`
+	RightMin    string `description:"图表右侧最小值"`
+	RightMax    string `description:"图表右侧最大值"`
+	Right2Min   string `description:"图表右侧最小值"`
+	Right2Max   string `description:"图表右侧最大值"`
+	MinMaxSave  int    `description:"是否手动保存过上下限:0-否;1-是"`
+	StartYear   int    `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+}

+ 82 - 0
models/data_manage/excel/excel_info_rule_mapping.go

@@ -0,0 +1,82 @@
+package excel
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ExcelInfoRuleMapping struct {
+	ExcelInfoRuleMappingId int       `orm:"pk" description:"主键"`
+	ExcelInfoId            int       `description:"Excel信息ID"`
+	RuleType               int       `description:"规则类型"`
+	LeftValue              string    `description:"左值"`
+	LeftValueBack          string    `description:"左值后端存储"`
+	LeftValueType          int       `description:"左值类型"`
+	RightValue             string    `description:"右值"`
+	RightValueBack         string    `description:"右值后端存储"`
+	RightValueType         int       `description:"右值类型"`
+	FontColor              string    `description:"字体颜色"`
+	BackgroundColor        string    `description:"背景颜色"`
+	Remark                 string    `description:"预设颜色说明"`
+	RemarkEn               string    `description:"预设颜色英文说明"`
+	Scope                  string    `description:"作用范围"`
+	ScopeCoord             string    `description:"作用范围坐标"`
+	ScopeShow              string    `description:"作用范围坐标前端显示"`
+	CreateTime             time.Time `description:"创建时间"`
+}
+
+type ExcelInfoRuleMappingView struct {
+	ExcelInfoRuleMappingId int    `orm:"pk" description:"主键"`
+	ExcelInfoId            int    `description:"Excel信息ID"`
+	RuleType               int    `description:"规则类型"`
+	LeftValue              string `description:"左值"`
+	LeftValueBack          string `description:"左值后端存储" json:"-"`
+	LeftValueType          int    `description:"左值类型"`
+	RightValue             string `description:"右值"`
+	RightValueBack         string `description:"右值后端存储" json:"-"`
+	RightValueType         int    `description:"右值类型"`
+	FontColor              string `description:"字体颜色"`
+	BackgroundColor        string `description:"背景颜色"`
+	Remark                 string `description:"预设颜色说明"`
+	RemarkEn               string `description:"预设颜色英文说明"`
+	Scope                  string `description:"作用范围"`
+	ScopeCoord             string `description:"作用范围坐标"`
+	ScopeShow              string `description:"作用范围坐标前端显示"`
+	CreateTime             string `description:"创建时间"`
+}
+
+func (e *ExcelInfoRuleMapping) Insert() (insertId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	insertId, err = o.Insert(e)
+	return
+}
+
+func (e *ExcelInfoRuleMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(e, cols...)
+	return
+}
+
+// GetExcelRuleMappingByExcelInfoId 根据excelInfoId获取规则映射信息
+func GetExcelRuleMappingByExcelInfoId(id int) (items []*ExcelInfoRuleMappingView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_info_rule_mapping WHERE excel_info_id = ? ORDER BY create_time ASC`
+	_, err = o.Raw(sql, id).QueryRows(&items)
+	return
+}
+
+// GetExcelRuleMappingById 根据主键Id获取规则映射信息
+func GetExcelRuleMappingById(id int) (item *ExcelInfoRuleMappingView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_info_rule_mapping WHERE excel_info_rule_mapping_id = ?`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func DeleteExcelRuleMappingById(id int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM excel_info_rule_mapping WHERE excel_info_rule_mapping_id = ?`
+	_, err = o.Raw(sql, id).Exec()
+	return
+}

+ 111 - 1
models/data_manage/excel/request/excel_info.go

@@ -1,5 +1,7 @@
 package request
 
+import "encoding/json"
+
 // MoveExcelInfoReq 移动excel表格请求
 type MoveExcelInfoReq struct {
 	ExcelClassifyId int `description:"excel表格分类id"`
@@ -61,7 +63,16 @@ type CalculateReq struct {
 
 // GetOtherEdbDateDataReq 获取其他指标的指定日期的数据
 type GetOtherEdbDateDataReq struct {
-	EdbInfoId int      `description:"指标id"`
+	EdbInfoId   int            `description:"指标id"`
+	SortType    string         `description:"如何排序,是正序还是倒序,枚举值:asc 正序,desc 倒序,不传默认倒序"`
+	DateList    []string       `description:"日期列表,从小到大"`
+	DateDecimal map[string]int `description:"日期行的小数位数映射"`
+}
+
+type GetBatchEdbDateDataReq struct {
+	EdbInfoId []int    `description:"指标id"`
+	Num       int      `description:"默认期数"`
+	Decimal   []int    `description:"指标上的小数位数"`
 	SortType  string   `description:"如何排序,是正序还是倒序,枚举值:asc 正序,desc 倒序,不传默认倒序"`
 	DateList  []string `description:"日期列表,从小到大"`
 }
@@ -71,6 +82,7 @@ type GetFutureDateDataReq struct {
 	EdbInfoIdList []int  `description:"指标id列表,从左至右,从上到下的顺序"`
 	DateType      int    `description:"日期类型,1:期数,2:截止日期"`
 	Num           int    `description:"需要添加的期数"`
+	Decimal       []int  `description:"小数位数, 从左至右,从上到下的顺序"`
 	StartDate     string `description:"开始日期"`
 	EndDate       string `description:"结束日期"`
 }
@@ -79,6 +91,7 @@ type GetFutureDateDataReq struct {
 type GetHistoryDateDataReq struct {
 	EdbInfoIdList []int  `description:"指标id列表,从左至右,从上到下的顺序"`
 	Num           int    `description:"需要添加的期数"`
+	Decimal       []int  `description:"小数位数, 从左至右,从上到下的顺序"`
 	EndDate       string `description:"结束日期"`
 }
 
@@ -96,6 +109,12 @@ type TableDataReq struct {
 	Sort          int               `description:"日期排序,1:倒序,2:正序"`
 	Data          []EdbInfoData     `description:"数据列表"`
 	TextRowData   [][]ManualDataReq `description:"文本列表"`
+	DecimalConfig []DecimalConfig   `description:"小数位数配置"`
+}
+type DecimalConfig struct {
+	Row     string `description:"行上的索引, 目前仅保存指标的日期"`
+	Col     int    `description:"列上的索引, 目前仅保存edbInfoId"`
+	Decimal int    `description:"小数位数, 从左至右,从上到下的顺序"`
 }
 
 // EdbInfoData 自定义表格的数据
@@ -108,12 +127,54 @@ type EdbInfoData struct {
 	Frequency        string          `description:"频度"`
 	Unit             string          `description:"单位"`
 	UnitEn           string          `description:"英文单位"`
+	Decimal          int             `description:"小数位数"`
+	ClassifyId       int             `description:"所属分类" json:"-"`
+	IsJoinPermission int             `description:"是否加入权限管控,0:不加入;1:加入;默认:0" json:"-"`
+	HaveOperaAuth    bool            `description:"是否有数据权限,默认:false"`
+	Data             []ManualDataReq `description:"单元格数据列表"`
+}
+type EdbInfoDataTemp struct {
+	EdbInfoId        int             `description:"指标ID"`
+	Tag              string          `description:"标签"`
+	EdbName          string          `description:"指标名称"`
+	EdbNameEn        string          `description:"英文指标名称"`
+	EdbAliasName     string          `description:"指标别名"`
+	Frequency        string          `description:"频度"`
+	Unit             string          `description:"单位"`
+	UnitEn           string          `description:"英文单位"`
+	Decimal          *int            `description:"小数位数"`
 	ClassifyId       int             `description:"所属分类" json:"-"`
 	IsJoinPermission int             `description:"是否加入权限管控,0:不加入;1:加入;默认:0" json:"-"`
 	HaveOperaAuth    bool            `description:"是否有数据权限,默认:false"`
 	Data             []ManualDataReq `description:"单元格数据列表"`
 }
 
+func (e *EdbInfoData) UnmarshalJSON(data []byte) error {
+	var alias EdbInfoDataTemp
+
+	if err := json.Unmarshal(data, &alias); err != nil {
+		return err
+	}
+	if alias.Decimal != nil {
+		e.Decimal = *alias.Decimal
+	} else {
+		e.Decimal = -1
+	}
+	e.EdbInfoId = alias.EdbInfoId
+	e.Tag = alias.Tag
+	e.EdbName = alias.EdbName
+	e.EdbNameEn = alias.EdbNameEn
+	e.EdbAliasName = alias.EdbAliasName
+	e.Frequency = alias.Frequency
+	e.Unit = alias.Unit
+	e.UnitEn = alias.UnitEn
+	e.ClassifyId = alias.ClassifyId
+	e.IsJoinPermission = alias.IsJoinPermission
+	e.HaveOperaAuth = alias.HaveOperaAuth
+	e.Data = alias.Data
+	return nil
+}
+
 // ManualDataReq 自定义表格的单元格数据
 type ManualDataReq struct {
 	DataType            int               `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算,5:预测值"`
@@ -121,9 +182,40 @@ type ManualDataReq struct {
 	DataTimeType        int               `description:"日期类型,1:实际日期;2:未来日期"`
 	ShowValue           string            `description:"展示值"`
 	Value               string            `description:"实际值(计算公式)"`
+	Decimal             int               `description:"小数位数"`
 	RelationEdbInfoList []RelationEdbInfo `description:"关联指标(计算公式中关联的指标,用于计算的时候去匹配)"`
 }
 
+type ManualDataTemp struct {
+	DataType            int               `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算,5:预测值"`
+	DataTime            string            `description:"所属日期"`
+	DataTimeType        int               `description:"日期类型,1:实际日期;2:未来日期"`
+	ShowValue           string            `description:"展示值"`
+	Value               string            `description:"实际值(计算公式)"`
+	Decimal             *int              `description:"小数位数"`
+	RelationEdbInfoList []RelationEdbInfo `description:"关联指标(计算公式中关联的指标,用于计算的时候去匹配)"`
+}
+
+func (m *ManualDataReq) UnmarshalJSON(data []byte) error {
+	var alias ManualDataTemp
+
+	if err := json.Unmarshal(data, &alias); err != nil {
+		return err
+	}
+	if alias.Decimal != nil {
+		m.Decimal = *alias.Decimal
+	} else {
+		m.Decimal = -1
+	}
+	m.DataType = alias.DataType
+	m.DataTime = alias.DataTime
+	m.DataTimeType = alias.DataTimeType
+	m.ShowValue = alias.ShowValue
+	m.Value = alias.Value
+	m.RelationEdbInfoList = alias.RelationEdbInfoList
+	return nil
+}
+
 // RelationEdbInfo 自定义表格中单元格的关联指标
 type RelationEdbInfo struct {
 	Tag string `description:"指标标签"`
@@ -160,3 +252,21 @@ type ShareExcelInfoReq struct {
 	ViewUserIds []int `description:"查看权限用户IDs"`
 	EditUserIds []int `description:"编辑权限用户IDs"`
 }
+
+type ExcelRuleMappingReq struct {
+	ExcelRuleMappingId int    `description:"规则映射ID"`
+	ExcelInfoId        int    `description:"ETA表格ID"`
+	RuleType           int    `description:"规则类型:1-大于,2-小于,3-介于,4-等于,5-发生日期"`
+	LeftValue          string `description:"条件值"`
+	LeftValueType      int    `description:"条件值的类型,1:数值,2:坐标"`
+	RightValue         string `description:"条件值, 用于介于的条件"`
+	RightValueType     int    `description:"条件值的类型,1:数值,2:坐标"`
+	FontColor          string `description:"字体颜色"`
+	BackgroundColor    string `description:"背景颜色"`
+	Remark             string `description:"预设的单元格样式名称"`
+	Scope              string `description:"应用选区"`
+}
+
+type DeleteExcelRuleMappingReq struct {
+	ExcelRuleMappingId int `description:"规则映射ID"`
+}

+ 7 - 2
models/data_manage/excel/request/mixed_table.go

@@ -133,8 +133,13 @@ type MixDateCalculateTagReq struct {
 
 // MixCellShowStyle 混合表格 单元格样式展示计算
 type MixCellShowStyle struct {
-	Pn int    `description:"小数点位数增加或减少,正数表述增加,负数表示减少" json:"pn"`
-	Nt string `description:"变换类型:number 小数点位数改变,percent百分比," json:"nt"`
+	Pn              int         `description:"小数点位数增加或减少,正数表述增加,负数表示减少" json:"pn"`
+	Nt              string      `description:"变换类型:number 小数点位数改变,percent百分比," json:"nt"`
+	GlObj           interface{} `description:"公式对象:1:数值,2:百分比,3:文本" json:"glObj"`
+	Decimal         *int        `description:"小数点位数"`
+	Last            string      `description:"起始操作:nt|decimal"`
+	Color           string      `description:"颜色值,#RRG" json:"color"`
+	BackgroundColor string      `description:"背景颜色值,#RRG" json:"background-color"`
 }
 
 type DateDataBeforeAfterReq struct {

+ 7 - 1
models/data_manage/excel/response/excel_info.go

@@ -4,8 +4,9 @@ import (
 	excel2 "eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/services/excel"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // AddExcelInfoResp 添加excel表格的返回
@@ -49,6 +50,7 @@ type TableCellResp struct {
 type TableDataItem struct {
 	EdbInfoId     int                     `description:"指标id"`
 	Data          []request.ManualDataReq `description:"数据列表"`
+	Decimal       int                     `description:"小数位数"`
 	ExcelSource   string                  `description:"表格来源str"`
 	ExcelSourceEn string                  `description:"表格来源(英文)"`
 }
@@ -118,3 +120,7 @@ type ShareExcelInfoDetail struct {
 	ViewUserIds []int `description:"查看权限用户IDs"`
 	EditUserIds []int `description:"编辑权限用户IDs"`
 }
+
+type ExcelRuleListResp struct {
+	List []*excel2.ExcelInfoRuleMappingView
+}

+ 62 - 0
models/data_manage/factor_edb_series.go

@@ -360,6 +360,68 @@ func (a FactorEdbSeriesCorrelationMatrixOrder) Less(i, j int) bool {
 	return a[i].XData > a[j].XData
 }
 
+// RemoveSeriesAndMappingByFactorEdbSeriesId 删除系列和指标关联
+func (m *FactorEdbSeries) RemoveSeriesAndMappingByFactorEdbSeriesId(factorEdbSeriesChartMapping *FactorEdbSeriesChartMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+	factorEdbSeriesId := factorEdbSeriesChartMapping.FactorEdbSeriesId
+	err = factorEdbSeriesChartMapping.Remove()
+	if err != nil {
+		err = fmt.Errorf("factorEdbSeriesChartMapping.delete err: %v", err)
+		return
+	}
+	if factorEdbSeriesId == 0 {
+		return
+	}
+
+	// 清除原指标关联
+	seriesOb := new(FactorEdbSeries)
+	cond := fmt.Sprintf("%s = ?", seriesOb.Cols().PrimaryId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, factorEdbSeriesId)
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, seriesOb.TableName(), cond)
+	_, e = tx.Raw(sql, pars).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove FactorEdbSeries err: %v", e)
+		return
+	}
+
+	// 清除原指标关联
+	mappingOb := new(FactorEdbSeriesMapping)
+	cond1 := fmt.Sprintf("%s = ?", mappingOb.Cols().FactorEdbSeriesId)
+	pars1 := make([]interface{}, 0)
+	pars1 = append(pars1, factorEdbSeriesId)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s`, mappingOb.TableName(), cond1)
+	_, e = tx.Raw(sql, pars1).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+	dataOb := new(FactorEdbSeriesCalculateDataQjjs)
+	//删除原指标数据
+	cond2 := fmt.Sprintf("%s = ?", dataOb.Cols().FactorEdbSeriesId)
+	pars2 := make([]interface{}, 0)
+	pars2 = append(pars2, factorEdbSeriesId)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s`, dataOb.TableName(), cond2)
+	_, e = tx.Raw(sql, pars2).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+	return
+}
+
 // CalculateCorrelationMatrixPars 计算相关性矩阵参数
 type CalculateCorrelationMatrixPars struct {
 	BaseEdbInfoId int               `description:"标的指标ID"`

+ 210 - 0
models/data_manage/factor_edb_series_calculate_data_qjjs.go

@@ -0,0 +1,210 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// FactorEdbSeriesCalculateDataQjjs 因子指标系列-区间计算数据表
+type FactorEdbSeriesCalculateDataQjjs struct {
+	FactorEdbSeriesCalculateDataId int       `orm:"column(factor_edb_series_calculate_data_id);pk"`
+	FactorEdbSeriesId              int       `description:"因子指标系列ID"`
+	EdbInfoId                      int       `description:"指标ID"`
+	EdbCode                        string    `description:"指标编码"`
+	DataTime                       time.Time `description:"数据日期"`
+	Value                          float64   `description:"数据值"`
+	CreateTime                     time.Time `description:"创建时间"`
+	ModifyTime                     time.Time `description:"修改时间"`
+	DataTimestamp                  int64     `description:"数据日期时间戳"`
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) TableName() string {
+	return "factor_edb_series_calculate_data_qjjs"
+}
+
+type FactorEdbSeriesCalculateDataQjjsCols struct {
+	PrimaryId         string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbCode           string
+	DataTime          string
+	Value             string
+	CreateTime        string
+	ModifyTime        string
+	DataTimestamp     string
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Cols() FactorEdbSeriesCalculateDataQjjsCols {
+	return FactorEdbSeriesCalculateDataQjjsCols{
+		PrimaryId:         "factor_edb_series_calculate_data_id",
+		FactorEdbSeriesId: "factor_edb_series_id",
+		EdbInfoId:         "edb_info_id",
+		EdbCode:           "edb_code",
+		DataTime:          "data_time",
+		Value:             "value",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+		DataTimestamp:     "data_timestamp",
+	}
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesCalculateDataId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) CreateMulti(items []*FactorEdbSeriesCalculateDataQjjs) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(500, items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesCalculateDataId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetItemById(id int) (item *FactorEdbSeriesCalculateDataQjjs, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesCalculateDataQjjs, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesCalculateDataQjjs, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesCalculateDataQjjs, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesCalculateDataQjjsItem 因子指标系列-计算数据信息
+type FactorEdbSeriesCalculateDataQjjsItem struct {
+	DataId            int     `description:"数据ID"`
+	FactorEdbSeriesId int     `description:"因子指标系列ID"`
+	EdbInfoId         int     `description:"指标ID"`
+	EdbCode           string  `description:"指标编码"`
+	DataTime          string  `description:"数据日期"`
+	Value             float64 `description:"数据值"`
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Format2Item() (item *FactorEdbSeriesCalculateDataQjjsItem) {
+	item = new(FactorEdbSeriesCalculateDataQjjsItem)
+	item.DataId = m.FactorEdbSeriesCalculateDataId
+	item.FactorEdbSeriesId = m.FactorEdbSeriesId
+	item.EdbInfoId = m.EdbInfoId
+	item.EdbCode = m.EdbCode
+	return
+}
+
+// TransEdbSeriesCalculateDataQjjs2EdbDataList 转换数据格式
+func TransEdbSeriesCalculateDataQjjs2EdbDataList(items []*FactorEdbSeriesCalculateDataQjjs) (list []*EdbDataList) {
+	list = make([]*EdbDataList, 0)
+	for _, v := range items {
+		list = append(list, &EdbDataList{
+			DataTime: v.DataTime.Format(utils.FormatDate),
+			Value:    v.Value,
+		})
+	}
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetEdbDataList(seriesId int, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
+	var pars []interface{}
+	sql := `SELECT factor_edb_series_calculate_data_id as edb_data_id  ,edb_info_id,data_time,value,data_timestamp FROM %s WHERE edb_info_id=? and factor_edb_series_id=? `
+	pars = append(pars, edbInfoId, seriesId)
+	if startDate != "" {
+		sql += ` AND data_time>=? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		sql += ` AND data_time<=? `
+		pars = append(pars, endDate)
+	}
+
+	sql += ` ORDER BY data_time ASC `
+	sql = fmt.Sprintf(sql, m.TableName())
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}

+ 8 - 0
models/data_manage/factor_edb_series_chart_mapping.go

@@ -10,6 +10,7 @@ import (
 
 const (
 	FactorEdbSeriesChartCalculateTypeCorrelation = 1 // 相关性计算
+	FactorEdbSeriesChartCalculateTypeRange       = 2 // 	区间计算
 )
 
 // FactorEdbSeriesChartMapping 因子指标系列-图表关联
@@ -120,6 +121,13 @@ func (m *FactorEdbSeriesChartMapping) GetItemById(id int) (item *FactorEdbSeries
 	return
 }
 
+func (m *FactorEdbSeriesChartMapping) GetItemByChartInfoId(id int) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().ChartInfoId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
 func (m *FactorEdbSeriesChartMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesChartMapping, err error) {
 	o := orm.NewOrmUsingDB("data")
 	order := ``

+ 7 - 0
models/data_manage/factor_edb_series_mapping.go

@@ -165,3 +165,10 @@ func (m *FactorEdbSeriesMapping) Format2Item() (item *FactorEdbSeriesMappingItem
 	item.EdbCode = m.EdbCode
 	return
 }
+
+func (m *FactorEdbSeriesMapping) GetItemBySeriesId(seriesId int) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().FactorEdbSeriesId)
+	_, err = o.Raw(sql, seriesId).QueryRows(&items)
+	return
+}

+ 52 - 1
models/data_manage/multiple_graph_config_edb_mapping.go

@@ -1,6 +1,7 @@
 package data_manage
 
 import (
+	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -10,7 +11,7 @@ type MultipleGraphConfigEdbMapping struct {
 	Id                    int       `orm:"column(id);pk"`
 	MultipleGraphConfigId int       `description:"多图配置id"`
 	EdbInfoId             int       `description:"指标id"`
-	Source                int       `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
+	Source                int       `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;10:区间计算指标"`
 	ModifyTime            time.Time `description:"最近一次修改时间"`
 	CreateTime            time.Time `description:"添加时间"`
 }
@@ -51,3 +52,53 @@ func GetMultipleGraphConfigEdbMappingListById(configId int) (items []*MultipleGr
 
 	return
 }
+
+func GetMultipleGraphConfigEdbMappingListByIdAndSource(configId, source int) (items []*MultipleGraphConfigEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? AND source = ? `
+	_, err = o.Raw(sql, configId, source).QueryRows(&items)
+
+	return
+}
+
+// AddMultipleGraphConfigEdbMappingList 新增多图配置
+func AddMultipleGraphConfigEdbMappingList(items []*MultipleGraphConfigEdbMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func DeleteMultipleGraphConfigEdbMappingByEdbIds(configId, source int, edbIds []int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? AND source = ? AND edb_info_id IN (` + utils.GetOrmInReplace(len(edbIds)) + `) `
+	_, err = o.Raw(sql, configId, source, edbIds).Exec()
+	return
+}
+
+func DeleteMultipleGraphConfigByChartInfoId(chartInfoId, configId, source int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := ` DELETE FROM multiple_graph_config WHERE multiple_graph_config_id = ?`
+	_, err = to.Raw(sql, configId).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` DELETE FROM multiple_graph_config_chart_mapping WHERE chart_info_id=? and multiple_graph_config_id = ? and	source=?`
+	_, err = to.Raw(sql, chartInfoId, configId, source).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` DELETE FROM  multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? and	source=? `
+	_, err = to.Raw(sql, configId, source).Exec()
+	return
+}

+ 15 - 13
models/db.go

@@ -506,15 +506,16 @@ func initChartFramework() {
 // initExcel 初始化EXCEL
 func initExcel() {
 	orm.RegisterModel(
-		new(excel.ExcelClassify),   //ETA excel表格分类
-		new(excel.ExcelInfo),       //ETA excel表格
-		new(excel.ExcelDraft),      //ETA excel表格草稿
-		new(excel.ExcelSheet),      //ETA excel sheet
-		new(excel.ExcelSheetData),  //ETA excel sheet data
-		new(excel.ExcelEdbMapping), //ETA excel 与 指标 的关系表
-		new(excel.ExcelWorker),     // 平衡表协作人表格
-		new(excel.ExcelChartEdb),   // 平衡表做图指标
-		new(excel.ExcelChartData),  // 平衡表作图数据
+		new(excel.ExcelClassify),        //ETA excel表格分类
+		new(excel.ExcelInfo),            //ETA excel表格
+		new(excel.ExcelDraft),           //ETA excel表格草稿
+		new(excel.ExcelSheet),           //ETA excel sheet
+		new(excel.ExcelSheetData),       //ETA excel sheet data
+		new(excel.ExcelEdbMapping),      //ETA excel 与 指标 的关系表
+		new(excel.ExcelWorker),          // 平衡表协作人表格
+		new(excel.ExcelChartEdb),        // 平衡表做图指标
+		new(excel.ExcelChartData),       // 平衡表作图数据
+		new(excel.ExcelInfoRuleMapping), //表格的管理规则
 	)
 }
 
@@ -627,10 +628,11 @@ func initFeCalendar() {
 // initFactorEdbSeries 因子指标系列数据表
 func initFactorEdbSeries() {
 	orm.RegisterModel(
-		new(data_manage.FactorEdbSeries),              // 因子指标系列
-		new(data_manage.FactorEdbSeriesChartMapping),  // 因子指标系列-图表关联
-		new(data_manage.FactorEdbSeriesMapping),       // 因子指标系列-指标计算数据
-		new(data_manage.FactorEdbSeriesCalculateData), // 因子指标系列-指标关联
+		new(data_manage.FactorEdbSeries),                  // 因子指标系列
+		new(data_manage.FactorEdbSeriesChartMapping),      // 因子指标系列-图表关联
+		new(data_manage.FactorEdbSeriesMapping),           // 因子指标系列-指标计算数据
+		new(data_manage.FactorEdbSeriesCalculateData),     // 因子指标系列-指标关联
+		new(data_manage.FactorEdbSeriesCalculateDataQjjs), // 因子指标系列-区间计算数据
 	)
 }
 

+ 243 - 0
routers/commentsRouter.go

@@ -1276,6 +1276,60 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "AddExcelRule",
+            Router: `/excel_info/rule/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "DeleteExcelRule",
+            Router: `/excel_info/rule/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetExcelRuleDetail",
+            Router: `/excel_info/rule/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "EditExcelRule",
+            Router: `/excel_info/rule/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetExcelRuleList",
+            Router: `/excel_info/rule/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetBatchEdbData",
+            Router: `/excel_info/table/batch_edb_data_list`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
         beego.ControllerComments{
             Method: "BatchRefresh",
@@ -2059,6 +2113,195 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/chart_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "BaseInfoEdit",
+            Router: `/chart_info/base/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Copy",
+            Router: `/chart_info/copy`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/chart_info/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "DetailFromUniqueCode",
+            Router: `/chart_info/detail/from_unique_code`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/chart_info/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/chart_info/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Preview",
+            Router: `/chart_info/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/chart_info/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoSave",
+            Router: `/chart_info/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/chart_info/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultipleGraphConfigEdbList",
+            Router: `/edb/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultipleGraphConfigSaveEdb",
+            Router: `/edb/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "AddChartClassify",
+            Router: `/chart_classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassify",
+            Router: `/chart_classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassifyCheck",
+            Router: `/chart_classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "EditChartClassify",
+            Router: `/chart_classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyItems",
+            Router: `/chart_classify/items`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyList",
+            Router: `/chart_classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyMove",
+            Router: `/chart_classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/chart_classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/supply_analysis:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/supply_analysis:VarietyController"],
         beego.ControllerComments{
             Method: "Add",

+ 7 - 0
routers/router.go

@@ -18,6 +18,7 @@ import (
 	future_good2 "eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
+	"eta/eta_api/controllers/data_manage/range_analysis"
 	"eta/eta_api/controllers/data_manage/supply_analysis"
 	"eta/eta_api/controllers/data_source"
 	"eta/eta_api/controllers/data_stat"
@@ -380,6 +381,12 @@ func init() {
 				&fe_calendar.FeCalendarMatterController{},
 			),
 		),
+		web.NSNamespace("/range_analysis",
+			web.NSInclude(
+				&range_analysis.RangeChartClassifyController{},
+				&range_analysis.RangeChartChartInfoController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 384 - 3
services/data/excel/excel_info.go

@@ -14,7 +14,10 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"sort"
+	"strings"
 	"time"
+
+	"github.com/xuri/excelize/v2"
 )
 
 // GetExcelDetailInfoByExcelInfoId 根据表格id获取表格详情
@@ -123,6 +126,7 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 			err = errors.New("获取最新的表格数据失败,Err:" + tmpErr.Error())
 			return
 		}
+		result = SetExcelByDecimalConfig(result, tableDataConfig.DecimalConfig)
 
 		if len(result.EdbInfoIdList) > 0 {
 			classifyIdList := make([]int, 0)
@@ -182,6 +186,39 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 	return
 }
 
+// SetExcelByDecimalConfig 设置表格的小数位
+func SetExcelByDecimalConfig(tableData request.TableDataReq, config []request.DecimalConfig) request.TableDataReq {
+	excelData := tableData.Data
+	edbInfoIndex := make(map[int]int)
+	dateIndex := make(map[string]int)
+	for i, v := range excelData {
+		edbInfoIndex[v.EdbInfoId] = i
+	}
+	for i, v := range excelData[0].Data {
+		dateIndex[v.DataTime] = i
+	}
+	for _, conf := range config {
+		if conf.Col > 0 {
+			if v, ok := edbInfoIndex[conf.Col]; ok {
+				excelData[v].Decimal = conf.Decimal
+				for i := 0; i < len(excelData[v].Data); i++ {
+					excelData[v].Data[i].Decimal = conf.Decimal
+				}
+			}
+		}
+		if conf.Row != "" {
+			if v, ok := dateIndex[conf.Row]; ok {
+				for i := 0; i < len(excelData); i++ {
+					excelData[i].Data[v].Decimal = conf.Decimal
+				}
+			}
+		}
+	}
+	tableData.Data = excelData
+	tableData.DecimalConfig = config
+	return tableData
+}
+
 // GetExcelInfoOpButton 获取ETA表格的操作权限
 func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int, haveOperaAuth bool) (button excel.ExcelInfoDetailButton) {
 	// 如果没有数据权限,那么直接返回
@@ -213,8 +250,9 @@ func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int, haveO
 }
 
 // GetFirstEdbDataList 获取第一列的数据
-func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList []string) (resultDataList []request.ManualDataReq, err error) {
+func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList []string, decimal int) (resultDataList []request.ManualDataReq, err error) {
 	var dataList []*data_manage.EdbDataList
+	resultDataList = make([]request.ManualDataReq, 0)
 	switch edbInfo.EdbInfoType {
 	case 0:
 		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
@@ -286,6 +324,7 @@ func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList [
 			DataTime:     dataList[lenData-i].DataTime,
 			ShowValue:    fmt.Sprint(dataList[lenData-i].Value),
 			Value:        fmt.Sprint(dataList[lenData-i].Value),
+			Decimal:      decimal,
 			DataTimeType: 1,
 		})
 	}
@@ -293,8 +332,160 @@ func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList [
 	return
 }
 
+// PadFirstEdbDataList 补齐第一列的数据
+func PadFirstEdbDataList(resultDataList []request.ManualDataReq, dateList []string, sortType string, decimal int) []request.ManualDataReq {
+	originDataNum := len(resultDataList)
+	requsetDateNum := len(dateList)
+	if originDataNum >= requsetDateNum {
+		return resultDataList
+	}
+	padNum := requsetDateNum - originDataNum
+	if sortType == "asc" {
+		for i := 0; i < padNum; i++ {
+			resultDataList = append(resultDataList, request.ManualDataReq{
+				DataType:     0,
+				DataTime:     dateList[originDataNum+i],
+				Decimal:      decimal,
+				ShowValue:    ``,
+				Value:        ``,
+				DataTimeType: 1,
+			})
+		}
+	} else {
+		var tmpDateList []request.ManualDataReq
+		for i := padNum - 1; i <= 0; i-- {
+			tmpDateList = append(tmpDateList, request.ManualDataReq{
+				DataType:     0,
+				DataTime:     dateList[originDataNum+i],
+				Decimal:      decimal,
+				ShowValue:    ``,
+				Value:        ``,
+				DataTimeType: 1,
+			})
+			resultDataList = append(tmpDateList, resultDataList...)
+		}
+
+	}
+	return resultDataList
+}
+
+// GetOtherEdbDataListFollowDate 获取其他列的数据,并设置日期的小数位数
+func GetOtherEdbDataListFollowDate(edbInfo *data_manage.EdbInfo, dateList []string, dateDecimal map[string]int) (resultDataList []request.ManualDataReq, err error) {
+	lenDate := len(dateList)
+	if lenDate <= 0 {
+		return
+	}
+	sortDateList := dateList
+	baseDateList := utils.StrArr{}
+	baseDateList = append(baseDateList, sortDateList...)
+	sort.Sort(baseDateList)
+	sortDateList = append([]string{}, baseDateList...)
+
+	endDateTime, err := time.ParseInLocation(utils.FormatDate, sortDateList[0], time.Local)
+	if err != nil {
+		return
+	}
+
+	firstDateTime, err := time.ParseInLocation(utils.FormatDate, sortDateList[lenDate-1], time.Local)
+	if err != nil {
+		return
+	}
+
+	var dataList []*data_manage.EdbDataList
+	switch edbInfo.EdbInfoType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+	default:
+		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+	}
+	if err != nil {
+		return
+	}
+
+	// 获取日期内的数据(包含开始日期前一个日期,以及 结束日期后一个日期,目的为了做空日期时的 插值法兼容)
+	baseDataList := make([]*data_manage.EdbDataList, 0)
+	var lastData *data_manage.EdbDataList
+	var isInsert bool
+	for _, data := range dataList {
+		tmpDate := data.DataTime
+		tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDate, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		if tmpDateTime.Before(firstDateTime) {
+			lastData = data
+			continue
+		}
+
+		// 如果是第一次写入数据
+		if !isInsert && lastData != nil {
+			baseDataList = append(baseDataList, lastData)
+		}
+
+		if tmpDateTime.After(endDateTime) {
+			baseDataList = append(baseDataList, data)
+			break
+		}
+		baseDataList = append(baseDataList, data)
+		isInsert = true
+	}
+
+	// 实际数据的日期map
+	realValMap := make(map[string]string)
+	for _, v := range baseDataList {
+		realValMap[v.DataTime] = v.DataTime
+	}
+
+	// 插值法处理
+	handleDataMap := make(map[string]float64)
+	err = data.HandleDataByLinearRegression(baseDataList, handleDataMap)
+	if err != nil {
+		return
+	}
+
+	latestDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfo.LatestDate, time.Local)
+
+	// 对于不存在的数据做补充
+	for _, date := range sortDateList {
+		dataType := 1
+		if _, ok := realValMap[date]; !ok {
+			dataType = 2
+		} else {
+			dataTime, _ := time.ParseInLocation(utils.FormatDate, date, time.Local)
+			// 如果是预测指标,且当前值的日期,晚于实际日期,那么是预测值
+			if edbInfo.EdbInfoType == 1 && dataTime.After(latestDateTime) {
+				dataType = 5
+			}
+		}
+		var value, showValue string
+		if tmpVal, ok := handleDataMap[date]; ok {
+			value = fmt.Sprint(tmpVal)
+			showValue = value
+		} else {
+			dataType = 3
+		}
+		tmpData := request.ManualDataReq{
+			DataType:  dataType,
+			DataTime:  date,
+			ShowValue: showValue,
+			Value:     value,
+		}
+		if v, ok := dateDecimal[date]; ok {
+			tmpData.Decimal = v
+		} else {
+			tmpData.Decimal = -1
+		}
+		resultDataList = append(resultDataList, tmpData)
+	}
+
+	return
+}
+
 // GetOtherEdbDataList 获取其他列的数据
-func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resultDataList []request.ManualDataReq, err error) {
+func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string, decimal int) (resultDataList []request.ManualDataReq, err error) {
 	lenDate := len(dateList)
 	if lenDate <= 0 {
 		return
@@ -394,6 +585,7 @@ func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resul
 		resultDataList = append(resultDataList, request.ManualDataReq{
 			DataType:  dataType,
 			DataTime:  date,
+			Decimal:   decimal,
 			ShowValue: showValue,
 			Value:     value,
 		})
@@ -403,7 +595,7 @@ func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resul
 }
 
 // GetFirstHistoryEdbDataList 获取指标的历史的数据
-func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate string) (resultDataList []request.ManualDataReq, err error) {
+func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate string, decimal int) (resultDataList []request.ManualDataReq, err error) {
 	endDateTime, err := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
 	if err != nil {
 		return
@@ -452,6 +644,7 @@ func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate s
 		resultDataList = append(resultDataList, request.ManualDataReq{
 			DataType:  dataType,
 			DataTime:  dataList[lenData-i].DataTime,
+			Decimal:   decimal,
 			ShowValue: fmt.Sprint(dataList[lenData-i].Value),
 			Value:     fmt.Sprint(dataList[lenData-i].Value),
 		})
@@ -614,3 +807,191 @@ func GetCustomAnalysisOpButton(sysUser *system.Admin, belongUserId int, permissi
 	}
 	return
 }
+
+func parseExcelScopeCoord(scopeList []string) (scopeCoord string, err error) {
+	if len(scopeList) == 2 {
+		x1, y1, er := excelize.CellNameToCoordinates(scopeList[0])
+		if er != nil {
+			return "", er
+		}
+		x2, y2, er := excelize.CellNameToCoordinates(scopeList[1])
+		if er != nil {
+			return "", er
+		}
+		scopeCoord = fmt.Sprintf("%d,%d,%d,%d", x1, y1, x2, y2)
+	}
+	if len(scopeList) == 1 {
+		x1, y1, er := excelize.CellNameToCoordinates(scopeList[0])
+		if er != nil {
+			return "", er
+		}
+		scopeCoord = fmt.Sprintf("%d,%d", x1, y1)
+	}
+	return
+}
+
+func ExcelRuleFormat(req *request.ExcelRuleMappingReq, lang string) (res *excel.ExcelInfoRuleMapping, err error) {
+	res = new(excel.ExcelInfoRuleMapping)
+	if req.RuleType == 5 {
+		switch req.LeftValue {
+		// 今天
+		case "today":
+			res.LeftValueBack = req.LeftValue
+		// 明天
+		case "tomorrow":
+			res.LeftValueBack = req.LeftValue
+		// 最近7天
+		case "last7days":
+			res.LeftValueBack = req.LeftValue
+		// 上周
+		case "lastweek":
+			res.LeftValueBack = req.LeftValue
+		// 本周
+		case "thisweek":
+			res.LeftValueBack = req.LeftValue
+		// 下周
+		case "nextweek":
+			res.LeftValueBack = req.LeftValue
+		// 上月
+		case "lastmonth":
+			res.LeftValueBack = req.LeftValue
+		// 本月
+		case "thismonth":
+			res.LeftValueBack = req.LeftValue
+		// 下月
+		case "nextmonth":
+			res.LeftValueBack = req.LeftValue
+		default:
+			err = errors.New("发生日期规则错误")
+			return
+		}
+	}
+	// 格式化条件值
+	switch req.LeftValueType {
+	// 坐标
+	case 2:
+		x, y, err := excelize.CellNameToCoordinates(req.LeftValue)
+		if err != nil {
+			return nil, err
+		}
+		res.LeftValue = req.LeftValue
+		res.LeftValueBack = fmt.Sprintf("%d,%d", x, y)
+	default:
+		res.LeftValue = req.LeftValue
+		res.LeftValueBack = req.LeftValue
+	}
+	switch req.RightValueType {
+	// 坐标
+	case 2:
+		x, y, err := excelize.CellNameToCoordinates(req.RightValue)
+		if err != nil {
+			return nil, err
+		}
+		res.RightValue = req.RightValue
+		res.RightValueBack = fmt.Sprintf("%d,%d", x, y)
+	default:
+		res.RightValue = req.RightValue
+		res.RightValueBack = req.RightValue
+	}
+	if res.LeftValueBack == "" {
+		res.LeftValueBack = req.LeftValue
+	}
+	if res.RightValueBack == "" {
+		res.RightValueBack = req.RightValue
+	}
+	res.RuleType = req.RuleType
+	res.ExcelInfoId = req.ExcelInfoId
+	res.ExcelInfoRuleMappingId = req.ExcelRuleMappingId
+	res.LeftValueType = req.LeftValueType
+	res.RightValueType = req.RightValueType
+	res.FontColor = req.FontColor
+	res.BackgroundColor = req.BackgroundColor
+	res.Remark = req.Remark
+	res.RemarkEn = req.Remark
+	res.ScopeShow = req.Scope
+	scopeList := strings.Split(req.Scope, ":")
+	res.ScopeCoord, err = parseExcelScopeCoord(scopeList)
+	return
+}
+
+func AddExcelRule(req *request.ExcelRuleMappingReq, lang string) (err error) {
+	excelRule, err := ExcelRuleFormat(req, lang)
+	if err != nil {
+		return
+	}
+	if excelRule.ExcelInfoRuleMappingId != 0 {
+		return errors.New("规则已存在")
+	}
+	excelRule.CreateTime = time.Now()
+	_, err = excelRule.Insert()
+	return
+}
+
+func ModifyExcelRule(req *request.ExcelRuleMappingReq, lang string) (err error) {
+	excelInfo, err := excel.GetExcelRuleMappingById(req.ExcelRuleMappingId)
+	if err != nil {
+		return
+	}
+	editExcelInfo, err := ExcelRuleFormat(req, lang)
+	if err != nil {
+		return
+	}
+	var updateCols []string
+	if excelInfo.LeftValue != editExcelInfo.LeftValue {
+		updateCols = append(updateCols, "LeftValue")
+		updateCols = append(updateCols, "LeftValueBack")
+	}
+	if excelInfo.LeftValueType != editExcelInfo.LeftValueType {
+		updateCols = append(updateCols, "LeftValueType")
+	}
+	if excelInfo.RightValue != editExcelInfo.RightValue {
+		updateCols = append(updateCols, "RightValue")
+		updateCols = append(updateCols, "RightValueBack")
+	}
+	if excelInfo.RightValueType != editExcelInfo.RightValueType {
+		updateCols = append(updateCols, "RightValueType")
+	}
+	if excelInfo.FontColor != editExcelInfo.FontColor {
+		updateCols = append(updateCols, "FontColor")
+	}
+	if excelInfo.BackgroundColor != editExcelInfo.BackgroundColor {
+		updateCols = append(updateCols, "BackgroundColor")
+	}
+	if excelInfo.Remark != editExcelInfo.Remark {
+		updateCols = append(updateCols, "Remark")
+	}
+	if excelInfo.RemarkEn != editExcelInfo.RemarkEn {
+		updateCols = append(updateCols, "RemarkEn")
+	}
+	if excelInfo.ScopeShow != editExcelInfo.ScopeShow {
+		updateCols = append(updateCols, "ScopeCoord")
+		updateCols = append(updateCols, "ScopeShow")
+	}
+	if len(updateCols) > 0 {
+		err = editExcelInfo.Update(updateCols)
+	}
+
+	return
+}
+
+// GetExcelRuleList 获取规则列表
+func GetExcelRuleList(excelInfoId int) (resp *response.ExcelRuleListResp, err error) {
+	resp = new(response.ExcelRuleListResp)
+	excelInfoList, err := excel.GetExcelRuleMappingByExcelInfoId(excelInfoId)
+	if err != nil {
+		return
+	}
+	resp.List = excelInfoList
+	return
+}
+
+// GetExcelRuleDetail 获取规则列表
+func GetExcelRuleDetail(excelInfoMappingId int) (resp *excel.ExcelInfoRuleMappingView, err error) {
+	resp = new(excel.ExcelInfoRuleMappingView)
+	excelInfoDetail, err := excel.GetExcelRuleMappingById(excelInfoMappingId)
+	if err != nil {
+		return
+	}
+	resp = excelInfoDetail
+	return
+}

+ 26 - 2
services/data/excel/mixed_table.go

@@ -8,12 +8,13 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
 )
 
 // BaseCalculate
@@ -1192,6 +1193,21 @@ func handleMixCellShowStyle(showStyleList []string, calculateCellMap map[string]
 			}
 			_, e := strconv.ParseFloat(val, 64) // 将字符串转换成float类型
 			if e != nil {                       // 如果没有错误发生则返回true,说明该字符串是一个合法的数字
+				var styleConf request.MixCellShowStyle
+				err = json.Unmarshal([]byte(cell.ShowStyle), &styleConf)
+				if err != nil {
+					err = fmt.Errorf("日期计算配置json解析失败失败: %s, Err:%s", config, err.Error())
+					return
+				}
+				// 前端传过来的json中有可能有glObj,需要去掉
+				if styleConf.GlObj != nil {
+					styleConf.GlObj = nil
+					tmpStyleConf, err := json.Marshal(styleConf)
+					if err == nil {
+						cell.ShowStyle = string(tmpStyleConf)
+					}
+				}
+				config[cellPosition.Column][cellPosition.Row] = cell
 				continue
 			}
 			var styleConf request.MixCellShowStyle
@@ -1206,6 +1222,14 @@ func handleMixCellShowStyle(showStyleList []string, calculateCellMap map[string]
 			} else {
 				cell.ShowFormatValue = cell.ShowValue
 			}
+			// 前端传过来的json中有可能有glObj,需要去掉
+			if styleConf.GlObj != nil {
+				styleConf.GlObj = nil
+				tmpStyleConf, err := json.Marshal(styleConf)
+				if err == nil {
+					cell.ShowStyle = string(tmpStyleConf)
+				}
+			}
 			config[cellPosition.Column][cellPosition.Row] = cell
 		}
 	}

+ 11 - 4
services/data/excel/time_table.go

@@ -7,12 +7,13 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
 )
 
 // TableDataConfig
@@ -26,6 +27,7 @@ type TableDataConfig struct {
 	ManualDate       []string                  `description:"手动配置的日期(未来的日期)"`
 	TableEdbInfoList []TableEdbInfo            `description:"表格内指标信息"`
 	TextRowData      [][]request.ManualDataReq `description:"文本列表"`
+	DecimalConfig    []request.DecimalConfig   `description:"小数位数配置"`
 }
 
 // TableEdbInfo
@@ -39,6 +41,7 @@ type TableEdbInfo struct {
 	Frequency    string `description:"频度"`
 	Unit         string `description:"单位"`
 	UnitEn       string `description:"英文单位"`
+	Decimal      int    `description:"小数位数"`
 }
 
 // ManualData
@@ -101,7 +104,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 		err = errors.New("找不到A列指标")
 		return
 	}
-	baseFirstEdbInfoDataList, err := GetFirstEdbDataList(firstEdbInfo, tableDataConfig.Num, manualDateList)
+	baseFirstEdbInfoDataList, err := GetFirstEdbDataList(firstEdbInfo, tableDataConfig.Num, manualDateList, -1)
 	if err != nil {
 		return
 	}
@@ -166,7 +169,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 			err = errors.New("找不到A列指标")
 			return
 		}
-		otherDataList, tmpErr := GetOtherEdbDataList(tmpEdbInfo, dateList)
+		otherDataList, tmpErr := GetOtherEdbDataList(tmpEdbInfo, dateList, -1)
 		if tmpErr != nil {
 			err = tmpErr
 			return
@@ -249,6 +252,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 			EdbNameEn:    tableEdbInfo.EdbNameEn,
 			EdbAliasName: tableEdbInfo.EdbAliasName,
 			Frequency:    tableEdbInfo.Frequency,
+			Decimal:      tableEdbInfo.Decimal,
 			Unit:         tableEdbInfo.Unit,
 			UnitEn:       tableEdbInfo.UnitEn,
 			Data:         manualDataReqList,
@@ -277,6 +281,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 				manualDataReqList = append(manualDataReqList, request.ManualDataReq{
 					DataType:            3,
 					DataTime:            tmpDateTimeStr,
+					Decimal:             tableEdbInfo.Decimal,
 					DataTimeType:        dataTimeType,
 					ShowValue:           "",
 					Value:               "",
@@ -290,6 +295,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 				manualDataReqList = append(manualDataReqList, request.ManualDataReq{
 					DataType:            3,
 					DataTime:            tmpDateTimeStr,
+					Decimal:             tableEdbInfo.Decimal,
 					DataTimeType:        dataTimeType,
 					ShowValue:           "",
 					Value:               "",
@@ -374,6 +380,7 @@ func GetTableDataConfig(reqData request.TableDataReq) (tableDataConfig TableData
 			Frequency:    v.Frequency,
 			Unit:         v.Unit,
 			UnitEn:       v.UnitEn,
+			Decimal:      v.Decimal,
 		}
 		tableEdbInfoList = append(tableEdbInfoList, tmpTableEdbInfo)
 

+ 2247 - 0
services/data/range_analysis/chart_info.go

@@ -0,0 +1,2247 @@
+package range_analysis
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// GetAutoCalculateDateDataList 获取当前时间相关的区间作为计算依据
+func GetAutoCalculateDateDataList(currentDate string, dataList []*data_manage.EdbDataList, req *data_manage.ChartRangeAnalysisExtraConf) (newDataList []*data_manage.EdbDataList, err error) {
+	currentDateTime, _ := time.ParseInLocation(utils.FormatDate, currentDate, time.Local)
+	switch req.DateRangeType {
+	case 0:
+		// 智能划分得到一个开始日期,和结束日期
+		var startDateTime time.Time
+		if req.AutoDateConf.IsAutoStartDate == 0 { //固定设置
+			startDate := req.AutoDateConf.StartDate
+			if startDate == "" {
+				startDate = "2020-01-01"
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		} else {
+			startConf := req.AutoDateConf.StartDateConf
+			startDate := ""
+			if startConf.BaseDateType == 0 { //
+				startDate = currentDate
+			} else if startConf.BaseDateType == 1 {
+				startDate = time.Now().Format(utils.FormatDate)
+			}
+			if startConf.MoveForward > 0 {
+				startDate = GetEdbDateByMoveForward(startDate, startConf.MoveForward, dataList)
+			}
+			if len(startConf.DateChange) > 0 {
+				startDate, err = HandleEdbDateChange(startDate, startConf.DateChange)
+				if err != nil {
+					err = fmt.Errorf("智能划分开始日期处理失败:%s", err.Error())
+					return
+				}
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		}
+		var calStartTime, calEndTime time.Time
+		if currentDateTime.Before(startDateTime) {
+			calStartTime = currentDateTime
+			calEndTime = startDateTime
+		} else {
+			calStartTime = startDateTime
+			calEndTime = currentDateTime
+		}
+		// 根据日期,获取数据
+		for _, vv := range dataList {
+			dataTimeT, _ := time.ParseInLocation(utils.FormatDate, vv.DataTime, time.Local)
+			if (dataTimeT.After(calStartTime) && dataTimeT.Before(calEndTime)) ||
+				dataTimeT.Equal(calStartTime) ||
+				dataTimeT.Equal(calEndTime) {
+				newDataList = append(newDataList, vv)
+			}
+		}
+	}
+	return
+}
+
+// HandleDataByCalculateType 根据计算公式处理数据
+func HandleDataByCalculateType(originList []*data_manage.ChartRangeAnalysisDateDataItem, originDataList []*data_manage.EdbDataList, req *data_manage.ChartRangeAnalysisExtraConf) (newList []*data_manage.EdbDataList, err error) {
+	if len(originList) == 0 {
+		return
+	}
+	calculateType := req.CalculateType
+	switch calculateType {
+	case 0: //均值
+		var sum float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					sum = 0
+					//计算的数据返回需要重新确定
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for _, vv := range calDataList {
+						sum += vv.Value
+					}
+					val := sum / float64(len(calDataList))
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				sum = 0
+				for k, v := range item.DataList {
+					sum += v.Value
+					val := sum / float64(k+1)
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		}
+
+	case 1: //累计值
+		var sum float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				sum = 0
+				for _, v := range item.DataList {
+					sum = 0
+					//计算的数据返回需要重新确定
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for _, vv := range calDataList {
+						sum += vv.Value
+					}
+					val := sum
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				sum = 0
+				for _, v := range item.DataList {
+					sum += v.Value
+					val := sum
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		}
+
+	case 2: //涨幅
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					var baseVal float64
+					//计算的数据返回需要重新确定
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					if len(calDataList) == 0 {
+						continue
+					}
+					baseVal = calDataList[0].Value
+					baseDate := calDataList[0].DataTime
+					if baseVal == 0 {
+						continue
+					}
+					if v.DataTime == baseDate {
+						continue
+					}
+
+					val := (v.Value - baseVal) / baseVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				if len(item.DataList) == 0 {
+					break
+				}
+				baseVal := item.DataList[0].Value
+				baseDate := item.DataList[0].DataTime
+				if baseVal == 0 {
+					break
+				}
+				for _, v := range item.DataList {
+					if v.DataTime == baseDate {
+						continue
+					}
+					val := (v.Value - baseVal) / baseVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		}
+	case 3: //复合增长率
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					var baseVal float64
+					var baseDate string
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					if len(calDataList) == 0 {
+						continue
+					}
+					baseVal = calDataList[0].Value
+					baseDate = calDataList[0].DataTime
+					if v.DataTime == baseDate {
+						continue
+					}
+					if baseVal == 0 {
+						continue
+					}
+
+					baseDateT, e := time.ParseInLocation(utils.FormatDate, baseDate, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					tmpT, e := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					// 计算两个日期相差的天数
+					diff := tmpT.Sub(baseDateT).Hours() / 24 / 365
+					val := v.Value / baseVal
+					val = math.Pow(val, 1/diff) - 1
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				if len(item.DataList) == 0 {
+					break
+				}
+				baseVal := item.DataList[0].Value
+				baseDate := item.DataList[0].DataTime
+				if baseVal == 0 {
+					break
+				}
+				for _, v := range item.DataList {
+					if v.DataTime == baseDate {
+						continue
+					}
+					baseDateT, e := time.ParseInLocation(utils.FormatDate, baseDate, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					tmpT, e := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					// 计算两个日期相差的天数
+					diff := tmpT.Sub(baseDateT).Hours() / 24 / 365
+					val := v.Value / baseVal
+					val = math.Pow(val, 1/diff) - 1
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		}
+	case 4: //最大值
+		var maxVal float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for kk, vv := range calDataList {
+						if kk == 0 {
+							maxVal = vv.Value
+						}
+						if vv.Value > maxVal {
+							maxVal = vv.Value
+						}
+					}
+
+					val := maxVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				for k, v := range item.DataList {
+					if k == 0 {
+						maxVal = v.Value
+					}
+					if v.Value > maxVal {
+						maxVal = v.Value
+					}
+					val := maxVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		}
+	case 5: //最小值
+		var minVal float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for kk, vv := range calDataList {
+						if kk == 0 {
+							minVal = vv.Value
+						}
+						if vv.Value < minVal {
+							minVal = vv.Value
+						}
+					}
+					val := minVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				for k, v := range item.DataList {
+					if k == 0 {
+						minVal = v.Value
+					}
+					if v.Value < minVal {
+						minVal = v.Value
+					}
+					val := minVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// GetChartEdbInfoFormat 区间计算图表-获取指标信息
+func GetChartEdbInfoFormat(chartInfoId int, edbInfoMappingList []*data_manage.ChartEdbInfoMapping) (edbList []*data_manage.ChartEdbInfoMapping, err error) {
+	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
+	for _, edbInfoMapping := range edbInfoMappingList {
+		if edbInfoMapping == nil {
+			err = fmt.Errorf("指标信息有误")
+			return
+		}
+
+		edbInfoMapping.FrequencyEn = data.GetFrequencyEn(edbInfoMapping.Frequency)
+		if edbInfoMapping.Unit == `无` {
+			edbInfoMapping.Unit = ``
+		}
+		if chartInfoId <= 0 {
+			edbInfoMapping.IsAxis = 1
+			edbInfoMapping.LeadValue = 0
+			edbInfoMapping.LeadUnit = ""
+			edbInfoMapping.ChartEdbMappingId = 0
+			edbInfoMapping.ChartInfoId = 0
+			edbInfoMapping.IsOrder = false
+			edbInfoMapping.EdbInfoType = 1
+			edbInfoMapping.ChartStyle = ""
+			edbInfoMapping.ChartColor = ""
+			edbInfoMapping.ChartWidth = 0
+		} else {
+			edbInfoMapping.LeadUnitEn = data.GetLeadUnitEn(edbInfoMapping.LeadUnit)
+		}
+		edbList = append(edbList, edbInfoMapping)
+	}
+	return
+}
+
+// GetChartDataByEdbInfoList 区间计算图表-根据指标信息获取x轴和y轴
+func GetChartDataByEdbInfoList(chartInfoId int, dateType, startYear int, startDate, endDate string, edbInfoMappingList []*data_manage.ChartEdbInfoMapping, req *data_manage.ChartRangeAnalysisExtraConf) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, dataResp data_manage.ChartRangeAnalysisDataResp, err error) {
+	if chartInfoId > 0 && req.EdbInfoMode == 1 {
+		edbList, xEdbIdValue, dataResp, err = GetChartDataByEdbInfoListBySeries(chartInfoId, dateType, startYear, startDate, endDate, edbInfoMappingList, req)
+		return
+	}
+	for _, edbInfoMapping := range edbInfoMappingList {
+		edbInfoMapping, err = getChartDataByEdbInfo(edbInfoMapping, req)
+		if err != nil {
+			return
+		}
+		edbInfoMapping.ConvertUnit = edbInfoMapping.Unit
+		edbInfoMapping.ConvertEnUnit = edbInfoMapping.UnitEn
+		if req.CalculateType == 2 || req.CalculateType == 3 {
+			edbInfoMapping.ConvertUnit = "无"
+			edbInfoMapping.ConvertEnUnit = "无"
+		}
+		if req.DataConvertType > 0 && req.DataConvertConf.Unit != "" {
+			edbInfoMapping.ConvertUnit = req.DataConvertConf.Unit
+			edbInfoMapping.ConvertEnUnit = req.DataConvertConf.Unit
+		}
+		dataList := edbInfoMapping.DataList.([]*data_manage.EdbDataList)
+		// 处理上下限
+		var maxData, minData float64
+		if len(dataList) > 0 {
+			maxData = dataList[0].Value
+			minData = dataList[0].Value
+			for _, v := range dataList {
+				if v.Value > maxData {
+					maxData = v.Value
+				}
+				if v.Value < minData {
+					minData = v.Value
+				}
+			}
+		}
+		edbInfoMapping.MaxData = maxData
+		edbInfoMapping.MinData = minData
+		xEdbIdValue = append(xEdbIdValue, edbInfoMapping.EdbInfoId)
+	}
+	//根据时间类型来筛选最终的数据
+	yearMax := 0
+	if dateType == utils.DateTypeNYears {
+		for _, v := range edbInfoMappingList {
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			if len(dataList) > 0 {
+				latestDate := dataList[len(dataList)-1].DataTime
+				if latestDate != "" {
+					lastDateT, tErr := time.Parse(utils.FormatDate, latestDate)
+					if tErr != nil {
+						err = fmt.Errorf("获取图表日期信息失败,Err:" + tErr.Error())
+						return
+					}
+					if lastDateT.Year() > yearMax {
+						yearMax = lastDateT.Year()
+					}
+				}
+			}
+		}
+	}
+	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
+
+	if startDate != "" {
+		for k, v := range edbInfoMappingList {
+			var maxData, minData float64
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			if len(dataList) == 0 {
+				newDataList = dataList
+			} else {
+				firstFlag := true
+				for _, d := range dataList {
+					if endDate != "" && d.DataTime > endDate {
+						continue
+					}
+					if d.DataTime < startDate {
+						continue
+					}
+					newDataList = append(newDataList, d)
+					if firstFlag {
+						maxData = d.Value
+						minData = d.Value
+						firstFlag = false
+					} else {
+						if d.Value > maxData {
+							maxData = d.Value
+						}
+						if d.Value < minData {
+							minData = d.Value
+						}
+					}
+				}
+			}
+			edbInfoMappingList[k].DataList = newDataList
+			edbInfoMappingList[k].MinData = minData
+			edbInfoMappingList[k].MaxData = maxData
+		}
+	}
+	dataResp = data_manage.ChartRangeAnalysisDataResp{ChartRangeAnalysisExtraConf: req}
+	if req.MultipleGraphConfigId > 0 {
+		//判断MultipleGraphConfigId和图表关系是否正确
+		multipleGraphConfigEdbMappingList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+			return
+		}
+		if len(multipleGraphConfigEdbMappingList) > 0 {
+			// 查询指标是否已存在,如果已经被删除了,则返回0值
+			edbIds := make([]int, 0)
+			for _, v := range multipleGraphConfigEdbMappingList {
+				edbIds = append(edbIds, v.EdbInfoId)
+			}
+			var condition string
+			var pars []interface{}
+			condition = " AND edb_info_id in (" + utils.GetOrmInReplace(len(edbIds)) + ") "
+			pars = append(pars, edbIds)
+			count, e := data_manage.GetEdbInfoCountByCondition(condition, pars)
+			if e != nil {
+				err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+				return
+			}
+			if count > 0 {
+				// 查询是否已经生成指标
+				dataResp.ConfigEdbNum = len(multipleGraphConfigEdbMappingList)
+			}
+		}
+	}
+	edbList, err = GetChartEdbInfoFormat(chartInfoId, edbInfoMappingList)
+	if err != nil {
+		err = fmt.Errorf("获取区间计算图表, 完善指标信息失败, Err:" + err.Error())
+		return
+	}
+	return
+}
+
+func GetChartDataByEdbInfoListBySeries(chartInfoId int, dateType, startYear int, startDate, endDate string, edbInfoMappingList []*data_manage.ChartEdbInfoMapping, req *data_manage.ChartRangeAnalysisExtraConf) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, dataResp data_manage.ChartRangeAnalysisDataResp, err error) {
+	//查询seriesId
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesMappingItem, err := chartSeriesOb.GetItemByChartInfoId(chartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = fmt.Errorf("图表关联关系不存在")
+			return
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	}
+	//根据seriesId查询数据
+	//并把数据放到dataList中
+	for _, edbInfoMapping := range edbInfoMappingList {
+		dataOb := new(data_manage.FactorEdbSeriesCalculateDataQjjs)
+		dataList, e := dataOb.GetEdbDataList(seriesMappingItem.FactorEdbSeriesId, edbInfoMapping.EdbInfoId, startDate, endDate)
+		if e != nil {
+			err = e
+			return
+		}
+		edbInfoMapping.ConvertUnit = edbInfoMapping.Unit
+		edbInfoMapping.ConvertEnUnit = edbInfoMapping.UnitEn
+		if req.CalculateType == 2 || req.CalculateType == 3 {
+			edbInfoMapping.ConvertUnit = "无"
+			edbInfoMapping.ConvertEnUnit = "无"
+		}
+		if req.DataConvertType > 0 && req.DataConvertConf.Unit != "" {
+			edbInfoMapping.ConvertUnit = req.DataConvertConf.Unit
+			edbInfoMapping.ConvertEnUnit = req.DataConvertConf.Unit
+		}
+		edbInfoMapping.DataList = dataList
+		// 处理上下限
+		var maxData, minData float64
+		if len(dataList) > 0 {
+			maxData = dataList[0].Value
+			minData = dataList[0].Value
+			for _, v := range dataList {
+				if v.Value > maxData {
+					maxData = v.Value
+				}
+				if v.Value < minData {
+					minData = v.Value
+				}
+			}
+		}
+		edbInfoMapping.MaxData = maxData
+		edbInfoMapping.MinData = minData
+		xEdbIdValue = append(xEdbIdValue, edbInfoMapping.EdbInfoId)
+	}
+	yearMax := 0
+	if dateType == utils.DateTypeNYears {
+		for _, v := range edbInfoMappingList {
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			latestDate := dataList[len(dataList)-1].DataTime
+			if latestDate != "" {
+				lastDateT, tErr := time.Parse(utils.FormatDate, latestDate)
+				if tErr != nil {
+					err = fmt.Errorf("获取图表日期信息失败,Err:" + tErr.Error())
+					return
+				}
+				if lastDateT.Year() > yearMax {
+					yearMax = lastDateT.Year()
+				}
+			}
+		}
+	}
+	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
+
+	if startDate != "" {
+		for k, v := range edbInfoMappingList {
+			var maxData, minData float64
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			if len(dataList) == 0 {
+				newDataList = dataList
+			} else {
+				firstFlag := true
+				for _, d := range dataList {
+					if endDate != "" && d.DataTime > endDate {
+						continue
+					}
+					if d.DataTime < startDate {
+						continue
+					}
+					newDataList = append(newDataList, d)
+					if firstFlag {
+						maxData = d.Value
+						minData = d.Value
+						firstFlag = false
+					} else {
+						if d.Value > maxData {
+							maxData = d.Value
+						}
+						if d.Value < minData {
+							minData = d.Value
+						}
+					}
+				}
+			}
+			edbInfoMappingList[k].DataList = newDataList
+			edbInfoMappingList[k].MinData = minData
+			edbInfoMappingList[k].MaxData = maxData
+		}
+	}
+	dataResp = data_manage.ChartRangeAnalysisDataResp{ChartRangeAnalysisExtraConf: req, SeriesId: seriesMappingItem.FactorEdbSeriesId}
+	// 查询配置关联关系
+	if req.MultipleGraphConfigId > 0 {
+		multipleGraphConfigEdbMappingList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+			return
+		}
+		if len(multipleGraphConfigEdbMappingList) > 0 {
+			// 查询指标是否已存在,如果已经被删除了,则返回0值
+			edbIds := make([]int, 0)
+			for _, v := range multipleGraphConfigEdbMappingList {
+				edbIds = append(edbIds, v.EdbInfoId)
+			}
+			var condition string
+			var pars []interface{}
+			condition = " and edb_info_id in (" + utils.GetOrmInReplace(len(edbIds)) + ") "
+			pars = append(pars, edbIds)
+			count, e := data_manage.GetEdbInfoCountByCondition(condition, pars)
+			if e != nil {
+				err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+				return
+			}
+			if count > 0 {
+				// 查询是否已经生成指标
+				dataResp.ConfigEdbNum = len(multipleGraphConfigEdbMappingList)
+			}
+		}
+	}
+
+	edbList, err = GetChartEdbInfoFormat(chartInfoId, edbInfoMappingList)
+	if err != nil {
+		err = fmt.Errorf("获取区间计算图表, 完善指标信息失败, Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// getChartDataByEdbInfo 区间计算图表-根据指标信息获取x轴和y轴
+func getChartDataByEdbInfo(edbInfoMapping *data_manage.ChartEdbInfoMapping, req *data_manage.ChartRangeAnalysisExtraConf) (newEdbInfoMapping *data_manage.ChartEdbInfoMapping, err error) {
+	newEdbInfoMapping = edbInfoMapping
+	// 指标的开始日期和结束日期
+	edbStartDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfoMapping.StartDate, time.Local)
+	//edbStartDate := edbStartDateTime.AddDate(0, 0, 1).Format(utils.FormatDate)
+	edbEndDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfoMapping.EndDate, time.Local)
+	edbEndDate := edbEndDateTime.Format(utils.FormatDate)
+
+	// 获取时间基准指标在时间区间内的值
+	dataList := make([]*data_manage.EdbDataList, 0)
+	switch edbInfoMapping.EdbInfoCategoryType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfoMapping.Source, edbInfoMapping.SubSource, edbInfoMapping.EdbInfoId, "", "")
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfoMapping.EdbInfoId, "", "", false)
+	default:
+		err = errors.New("指标base类型异常")
+		return
+	}
+	/*var dataList data_manage.SortEdbDataList
+	dataList = dataListTmp
+	ascDataList := dataListTmp
+	sort.Sort(dataList)*/
+	dateList := make([]*data_manage.ChartRangeAnalysisDateDataItem, 0)
+	switch req.DateRangeType {
+	case 0:
+		// 智能划分得到一个开始日期,和结束日期
+		var startDateTime, endDateTime time.Time
+		startDateTime = edbStartDateTime
+		if req.AutoDateConf.IsAutoStartDate == 0 { //固定设置
+			startDate := req.AutoDateConf.StartDate
+			if startDate == "" {
+				startDate = "2020-01-01"
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		} /*else {
+			startConf := req.AutoDateConf.StartDateConf
+			startDate := ""
+			if startConf.BaseDateType == 0 { //
+				startDate = edbEndDate
+			} else if startConf.BaseDateType == 1 {
+				startDate = time.Now().Format(utils.FormatDate)
+			}
+			if startConf.MoveForward > 0 {
+				startDate = GetEdbDateByMoveForward(startConf.MoveForward, dataList)
+			}
+			if len(startConf.DateChange) > 0 {
+				startDate, err = HandleEdbDateChange(startDate, startConf.DateChange)
+				if err != nil {
+					err = fmt.Errorf("智能划分开始日期处理失败:%s", err.Error())
+					return
+				}
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		}*/
+
+		if req.AutoDateConf.IsAutoEndDate == 0 { //固定设置
+			endDate := req.AutoDateConf.EndDate
+			if endDate == "" {
+				err = fmt.Errorf("智能划分截止日期处理失败:请输入截止日期")
+				return
+			}
+			// todo 如果截止日期比指标日期还要大,则用指标的最新日期
+			endDateTime, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+		} else {
+			endConf := req.AutoDateConf.EndDateConf
+			endDate := edbEndDate
+			if endConf.MoveForward > 0 {
+				endDate = GetEdbDateByMoveForward(endDate, endConf.MoveForward, dataList)
+			}
+			if len(endConf.DateChange) > 0 {
+				endDate, err = HandleEdbDateChange(endDate, endConf.DateChange)
+				if err != nil {
+					err = fmt.Errorf("智能划分结束日期处理失败:%s", err.Error())
+					return
+				}
+			}
+			endDateTime, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+		}
+
+		dateList = append(dateList, &data_manage.ChartRangeAnalysisDateDataItem{
+			StartDate: startDateTime,
+			EndDate:   endDateTime})
+	case 1:
+		// 手工划分得到多个开始日期和结束日期(已排序)
+		for _, v := range req.ManualDateConf {
+			startDateT, _ := time.ParseInLocation(utils.FormatDate, v.StartDate, time.Local)
+			endDateT, _ := time.ParseInLocation(utils.FormatDate, v.EndDate, time.Local)
+			tmp := &data_manage.ChartRangeAnalysisDateDataItem{
+				StartDate: startDateT,
+				EndDate:   endDateT,
+			}
+			dateList = append(dateList, tmp)
+		}
+	case 2:
+		// 跨年划分得到多个开始日期和结束日期
+		startYear := edbStartDateTime.Year()
+		endYear := edbEndDateTime.Year()
+		startDay := req.YearDateConf.StartDay
+		endDay := req.YearDateConf.EndDay
+		for year := startYear; year <= endYear; year++ {
+			startDate := fmt.Sprintf("%d-%s", year, startDay)
+			endDate := fmt.Sprintf("%d-%s", year+1, endDay)
+			startDateTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+			endDateTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+			if startDateTime.Before(edbStartDateTime) {
+				break
+			}
+
+			tmp := &data_manage.ChartRangeAnalysisDateDataItem{
+				StartDate: startDateTime,
+				EndDate:   endDateTime,
+			}
+			dateList = append(dateList, tmp)
+		}
+	}
+	// 根据日期,获取数据
+	for _, v := range dateList {
+		for _, vv := range dataList {
+			dataTimeT, _ := time.ParseInLocation(utils.FormatDate, vv.DataTime, time.Local)
+			if dataTimeT.After(v.StartDate) && dataTimeT.Before(v.EndDate) ||
+				dataTimeT.Equal(v.StartDate) ||
+				dataTimeT.Equal(v.EndDate) {
+				v.DataList = append(v.DataList, vv)
+			}
+		}
+	}
+	// 根据时间区间类型来获取数据的计算窗口,然后再拼接成整段数据
+	newDataList, err := HandleDataByCalculateType(dateList, dataList, req)
+	if err != nil {
+		return
+	}
+	if req.UnNormalDataDealType > 0 {
+		switch req.UnNormalDataDealType { //0:不处理,1:剔除,2替换
+		case 1:
+			dealDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range newDataList {
+				if !utils.CompareFloatByOpStrings(req.UnNormalDataConf.Formula, v.Value, req.UnNormalDataConf.Value) {
+					dealDataList = append(dealDataList, v)
+				}
+			}
+		case 2:
+			for i, v := range newDataList {
+				if utils.CompareFloatByOpStrings(req.UnNormalDataConf.Formula, v.Value, req.UnNormalDataConf.Value) {
+					newDataList[i].Value = req.UnNormalDataConf.ReplaceValue
+				}
+			}
+		}
+	}
+
+	if req.DataConvertType > 0 {
+		// 数据转换类型 0不转, 1乘 2除 3对数
+		switch req.DataConvertType {
+		case 1:
+			for i, v := range newDataList {
+				val := v.Value * req.DataConvertConf.Value
+				val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+				newDataList[i].Value = val
+			}
+			//item.MaxData = item.MaxData * v.ConvertValue
+			//item.MinData = item.MinData * v.ConvertValue
+		case 2:
+			for i, v := range newDataList {
+				val := v.Value / req.DataConvertConf.Value
+				val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+				newDataList[i].Value = val
+			}
+			//item.MaxData = item.MaxData / v.ConvertValue
+			//item.MinData = item.MinData / v.ConvertValue
+		case 3:
+			for i, v := range newDataList {
+				if v.Value <= 0 {
+					err = errors.New("数据中含有负数或0,无法对数运算")
+					return
+				}
+				val := math.Log(v.Value) / math.Log(req.DataConvertConf.Value)
+				val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+				newDataList[i].Value = val
+			}
+			//item.MaxData = math.Log(item.MaxData) / math.Log(v.ConvertValue)
+			//item.MinData = math.Log(item.MinData) / math.Log(v.ConvertValue)
+		}
+	}
+	newEdbInfoMapping.DataList = newDataList
+	//时间处理
+
+	return
+}
+
+// RollingCorrelationChartDataResp 滚动区间计算图表数据
+type RollingCorrelationChartDataResp struct {
+	MaxData             float64
+	MinData             float64
+	LatestDate          string `description:"真实数据的最后日期"`
+	EdbInfoCategoryType int
+	ChartColor          string
+	ChartStyle          string
+	PredictChartColor   string
+	ChartType           int
+	ChartWidth          int
+	EdbName             string
+	EdbNameEn           string
+	Unit                string
+	UnitEn              string
+	IsAxis              int
+	DataList            []data_manage.EdbDataList
+}
+
+// ChartInfoRefresh 图表刷新
+func ChartInfoRefresh(chartInfoId int, uniqueCode string) (isAsync bool, err error) {
+	var errMsg string
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("CorrelationChartInfoRefresh: %s", errMsg)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+	var chartInfo *data_manage.ChartInfo
+	if chartInfoId > 0 {
+		chartInfo, err = data_manage.GetChartInfoById(chartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "图表已被删除,请刷新页面"
+				err = errors.New(errMsg)
+				return
+			}
+			errMsg = "获取图表信息失败"
+			err = errors.New("获取图表信息失败,Err:" + err.Error())
+			return
+		}
+	} else {
+		chartInfo, err = data_manage.GetChartInfoByUniqueCode(uniqueCode)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "图表已被删除,请刷新页面"
+				err = errors.New(errMsg)
+				return
+			}
+			errMsg = "获取图表信息失败"
+			err = errors.New("获取图表信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	// 1.刷新图表关联的指标
+	mappings, e := data_manage.GetChartEdbMappingList(chartInfoId)
+	if e != nil {
+		utils.FileLog.Info(fmt.Sprintf("获取图表关联指标失败, err: %v", e))
+		return
+	}
+	if len(mappings) == 0 {
+		utils.FileLog.Info("图表无关联指标")
+		return
+	}
+	var edbIds []int
+	for _, v := range mappings {
+		edbIds = append(edbIds, v.EdbInfoId)
+	}
+	if e, _ = data.EdbInfoRefreshAllFromBaseV3(edbIds, false, true, false); e != nil {
+		utils.FileLog.Info(fmt.Sprintf("批量刷新指标失败, err: %v", e))
+		return
+	}
+
+	// todo 重新计算
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesMappingItem, err := chartSeriesOb.GetItemByChartInfoId(chartInfo.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	} else {
+		_, e = FactorEdbStepCalculateRange(seriesMappingItem.FactorEdbSeriesId, edbIds, extraConfig, true)
+		if e != nil {
+			err = fmt.Errorf("计算因子指标失败, Err: " + e.Error())
+			return
+		}
+	}
+	// 4.清除图表缓存
+	key := utils.HZ_CHART_LIB_DETAIL + uniqueCode
+	_ = utils.Rc.Delete(key)
+
+	return
+}
+
+// AddChartInfo 添加图表
+func AddChartInfo(req data_manage.AddChartInfoReq, source int, sysUser *system.Admin, lang string) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartName == "" {
+		errMsg = "请填写图表名称!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.ChartClassifyId <= 0 {
+		errMsg = "分类参数错误!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if req.ExtraConfig == `` {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg)
+		return
+	}
+	err = json.Unmarshal([]byte(req.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+
+	if len(req.ChartEdbInfoList) <= 0 {
+		errMsg = "请选择指标"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if len(req.ChartEdbInfoList) > 100 {
+		errMsg = "添加指标总数量不得超过100"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	err, errMsg, isSendEmail = CheckChartRangeExtraConfig(extraConfig)
+	if err != nil {
+		return
+	}
+
+	chartClassify, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if chartClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	var edbInfoIdArr []int
+
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoId := v.EdbInfoId
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(edbInfoId)
+		if tmpErr != nil {
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "指标不存在!"
+				err = errors.New("指标不存在,edbInfoId:" + strconv.Itoa(edbInfoId))
+				return
+			} else {
+				errMsg = "获取指标信息失败!"
+				err = errors.New("获取图表的指标信息失败,Err:" + tmpErr.Error())
+				return
+			}
+		}
+
+		if edbInfo == nil {
+			errMsg = "指标已被删除,请重新选择!"
+			err = errors.New("指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+			return
+		} else {
+			if edbInfo.EdbInfoId <= 0 {
+				errMsg = "指标已被删除,请重新选择!"
+				err = errors.New("指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+				return
+			}
+		}
+
+		edbInfoIdArr = append(edbInfoIdArr, edbInfoId)
+		edbInfo.EdbNameSource = edbInfo.EdbName
+	}
+
+	sort.Ints(edbInfoIdArr)
+	var edbInfoIdArrStr []string
+	for _, v := range edbInfoIdArr {
+		edbInfoIdArrStr = append(edbInfoIdArrStr, strconv.Itoa(v))
+	}
+	edbInfoIdStr := strings.Join(edbInfoIdArrStr, ",")
+	var chartInfoId int
+
+	// 判断图表是否存在
+	{
+		var condition string
+		var pars []interface{}
+
+		switch lang {
+		case utils.EnLangVersion:
+			condition += " AND chart_name_en = ? AND source = ? "
+		default:
+			condition += " AND chart_name=? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, source)
+		count, tmpErr := data_manage.GetChartInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断图表名称是否存在失败"
+			err = errors.New("判断图表名称是否存在失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		if count > 0 {
+			errMsg = "图表已存在,请重新填写"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	disableVal := data.CheckIsDisableChart(edbInfoIdArr)
+
+	chartInfo = new(data_manage.ChartInfo)
+	chartInfo.ChartName = req.ChartName
+	chartInfo.ChartNameEn = req.ChartName
+	chartInfo.EdbInfoIds = edbInfoIdStr
+	chartInfo.ChartClassifyId = req.ChartClassifyId
+	chartInfo.SysUserId = sysUser.AdminId
+	chartInfo.SysUserRealName = sysUser.RealName
+	chartInfo.CreateTime = time.Now()
+	chartInfo.ModifyTime = time.Now()
+	chartInfo.IsSetName = 0
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	chartInfo.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp)
+	chartInfo.ChartType = 1 // 区间计算图
+	chartInfo.Calendar = "公历"
+	chartInfo.DateType = req.DateType
+	chartInfo.StartDate = req.StartDate
+	chartInfo.EndDate = req.EndDate
+	chartInfo.StartYear = req.StartYear
+	chartInfo.SeasonStartDate = req.StartDate
+	chartInfo.SeasonEndDate = req.EndDate
+	chartInfo.LeftMin = req.LeftMin
+	chartInfo.LeftMax = req.LeftMax
+	chartInfo.RightMin = req.RightMin
+	chartInfo.RightMax = req.RightMax
+	chartInfo.Disabled = disableVal
+	chartInfo.Source = source
+	chartInfo.ChartThemeId = req.ChartThemeId
+	chartInfo.SourcesFrom = req.SourcesFrom
+	chartInfo.Instructions = req.Instructions
+	chartInfo.MarkersLines = req.MarkersLines
+	chartInfo.MarkersAreas = req.MarkersAreas
+	if req.ExtraConfig != "" {
+		chartInfo.ExtraConfig = req.ExtraConfig
+	}
+
+	// 指标信息
+	mapList := make([]*data_manage.ChartEdbMapping, 0)
+	for _, v := range req.ChartEdbInfoList {
+		// todo 指标名称修改
+		mapItem := new(data_manage.ChartEdbMapping)
+		mapItem.EdbInfoId = v.EdbInfoId
+		mapItem.EdbAliasName = v.EdbAliasName
+		mapItem.IsAxis = v.IsAxis
+		mapItem.CreateTime = time.Now()
+		mapItem.ModifyTime = time.Now()
+		edbTimestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		mapItem.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + edbTimestamp + "_" + strconv.Itoa(v.EdbInfoId))
+		mapItem.IsOrder = false
+		mapItem.EdbInfoType = 1
+		mapItem.Source = utils.CHART_SOURCE_RANGE_ANALYSIS
+		mapList = append(mapList, mapItem)
+
+	}
+
+	// 新增图表和指标mapping
+	chartInfoId, e := data_manage.CreateRangeChartAndEdb(chartInfo, mapList)
+	if e != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增区间计算图表失败, Err: " + e.Error())
+		return
+	}
+	// 把系列和图表绑定
+	if extraConfig.EdbInfoMode == 1 {
+		// 新增指标系列
+		err = AddSeries(edbInfoIdArr, chartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS, extraConfig, req.ExtraConfig)
+		if err != nil {
+			errMsg = "操作失败"
+			err = errors.New("新增区间计算图表失败, Err: " + err.Error())
+			return
+		}
+		//todo 如果保存失败是否要删除
+	}
+	/*//添加配置信息
+	multipleGraphConfig := &data_manage.MultipleGraphConfig{
+		//MultipleGraphConfigId: 0,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	err = data_manage.AddMultipleGraphConfig(multipleGraphConfig)
+	if err != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增区间计算图表配置失败, Err: " + err.Error())
+		return
+	}*/
+	if extraConfig.MultipleGraphConfigId > 0 {
+		multipleGraphConfigChartMapping := &data_manage.MultipleGraphConfigChartMapping{
+			//Id:                    0,
+			MultipleGraphConfigId: extraConfig.MultipleGraphConfigId,
+			ChartInfoId:           chartInfo.ChartInfoId,
+			Source:                utils.CHART_SOURCE_RANGE_ANALYSIS,
+			ModifyTime:            time.Now(),
+			CreateTime:            time.Now(),
+		}
+		err = data_manage.AddMultipleGraphConfigChartMapping(multipleGraphConfigChartMapping)
+		if err != nil {
+			errMsg = "操作失败"
+			err = errors.New("新增区间计算图表和配置关联关系失败, Err: " + err.Error())
+			return
+		}
+	}
+
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartInfoId)
+
+	return
+}
+
+// EditChartInfo 编辑图表
+func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin, lang string) (chartItem *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	chartItem, err = data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "图表已被删除,请刷新页面"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取图表信息失败"
+		err = errors.New("获取图表信息失败,Err:" + err.Error())
+		return
+	}
+
+	if chartItem.Source != utils.CHART_SOURCE_RANGE_ANALYSIS {
+		errMsg = "该图不是区间计算图表!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartClassifyId <= 0 {
+		errMsg = "分类参数错误!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if req.ExtraConfig == `` {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg)
+		return
+	}
+	err = json.Unmarshal([]byte(req.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg)
+		return
+	}
+
+	if len(req.ChartEdbInfoList) <= 0 {
+		errMsg = "请选择指标"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	extraConfig.SeriesName = strings.TrimSpace(extraConfig.SeriesName)
+	if len(req.ChartEdbInfoList) > 100 {
+		errMsg = "添加指标总数量不得超过100"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	err, errMsg, isSendEmail = CheckChartRangeExtraConfig(extraConfig)
+	if err != nil {
+		return
+	}
+	chartClassify, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if chartClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	// 图表操作权限
+	ok := data.CheckOpChartPermission(sysUser, chartItem.SysUserId, true)
+	if !ok {
+		errMsg = "没有该图表的操作权限"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	var edbInfoIdArr []int
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoId := v.EdbInfoId
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(edbInfoId)
+		if tmpErr != nil {
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "图表不存在!"
+				err = errors.New("图表指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+				return
+			} else {
+				errMsg = "获取图表信息失败!"
+				err = errors.New("获取图表的指标信息失败,Err:" + tmpErr.Error())
+				return
+			}
+		}
+
+		if edbInfo == nil {
+			errMsg = "指标不存在!"
+			err = errors.New("指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+			return
+		}
+		edbInfoIdArr = append(edbInfoIdArr, edbInfoId)
+	}
+
+	sort.Ints(edbInfoIdArr)
+	var edbInfoIdArrStr []string
+	for _, v := range edbInfoIdArr {
+		edbInfoIdArrStr = append(edbInfoIdArrStr, strconv.Itoa(v))
+	}
+	edbInfoIdStr := strings.Join(edbInfoIdArrStr, ",")
+
+	//判断图表是否存在
+	{
+		var condition string
+		var pars []interface{}
+		condition += " AND chart_info_id <> ? "
+		pars = append(pars, req.ChartInfoId)
+		switch lang {
+		case utils.EnLangVersion:
+			condition += " AND chart_name_en = ? AND source = ? "
+		default:
+			condition += " AND chart_name=? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, chartItem.Source)
+		count, tmpErr := data_manage.GetChartInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断图表名称是否存在失败"
+			err = errors.New("判断图表名称是否存在失败,Err:" + tmpErr.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "图表已存在,请重新填写"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+	// 修改图表与指标mapping
+	req.ChartType = 1
+	dateType := req.DateType
+	err = data_manage.EditRangeChartInfoAndMapping(&req, edbInfoIdStr, lang, "公历", dateType, 0, ``)
+	if err != nil {
+		errMsg = "保存失败"
+		err = errors.New("保存失败,Err:" + err.Error())
+		return
+	}
+
+	addSeries := false
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesMappingItem, err := chartSeriesOb.GetItemByChartInfoId(chartItem.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+			addSeries = true
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	}
+	if extraConfig.EdbInfoMode == 1 {
+		if addSeries {
+			// 新增指标系列
+			err = AddSeries(edbInfoIdArr, chartItem.ChartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS, extraConfig, req.ExtraConfig)
+			if err != nil {
+				errMsg = "操作失败"
+				err = errors.New("新增区间计算图表失败, Err: " + err.Error())
+				return
+			}
+		} else {
+			err = EditSeries(seriesMappingItem, edbInfoIdArr, extraConfig, req.ExtraConfig, true)
+			if err != nil {
+				errMsg = "保存失败"
+				err = errors.New("保存失败,Err:" + err.Error())
+				return
+			}
+		}
+		// todo 编辑失败处理
+	} else if extraConfig.EdbInfoMode == 0 {
+		if !addSeries {
+			// 删除相关的系列
+			factorSeriesOb := new(data_manage.FactorEdbSeries)
+			err = factorSeriesOb.RemoveSeriesAndMappingByFactorEdbSeriesId(seriesMappingItem)
+			if err != nil {
+				errMsg = "保存失败"
+				err = errors.New("保存失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartItem.ChartInfoId
+	resp.UniqueCode = chartItem.UniqueCode
+	resp.ChartType = req.ChartType
+
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	return
+}
+
+// CopyChartInfo 复制图表
+func CopyChartInfo(classifyId int, chartName string, oldChartInfo *data_manage.ChartInfo, sysUser *system.Admin, lang string) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	err = json.Unmarshal([]byte(oldChartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+
+	//新增配置,并绑定图表
+	multipleGraphConfig := &data_manage.MultipleGraphConfig{
+		//MultipleGraphConfigId: 0,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	err = data_manage.AddMultipleGraphConfig(multipleGraphConfig)
+	if err != nil {
+		errMsg = "新增区间计算图表配置失败"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+	extraConfig.MultipleGraphConfigId = multipleGraphConfig.MultipleGraphConfigId
+	newExtraConfigBytes, err := json.Marshal(extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+	newExtraConfig := string(newExtraConfigBytes)
+	chartInfo = &data_manage.ChartInfo{
+		ChartInfoId:     0,
+		ChartName:       chartName,
+		ChartClassifyId: classifyId,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		UniqueCode:      utils.MD5(utils.CHART_PREFIX + "_" + timestamp),
+		CreateTime:      time.Now(),
+		ModifyTime:      time.Now(),
+		DateType:        oldChartInfo.DateType,
+		StartDate:       oldChartInfo.StartDate,
+		EndDate:         oldChartInfo.EndDate,
+		IsSetName:       oldChartInfo.IsSetName,
+		EdbInfoIds:      oldChartInfo.EdbInfoIds,
+		ChartType:       oldChartInfo.ChartType,
+		Calendar:        oldChartInfo.Calendar,
+		SeasonStartDate: oldChartInfo.SeasonStartDate,
+		SeasonEndDate:   oldChartInfo.SeasonEndDate,
+		ChartImage:      oldChartInfo.ChartImage,
+		BarConfig:       oldChartInfo.BarConfig,
+		//Sort:     sort,
+		LeftMin:           oldChartInfo.LeftMin,
+		LeftMax:           oldChartInfo.LeftMax,
+		RightMin:          oldChartInfo.RightMin,
+		RightMax:          oldChartInfo.RightMax,
+		Right2Min:         oldChartInfo.Right2Min,
+		Right2Max:         oldChartInfo.Right2Max,
+		Disabled:          oldChartInfo.Disabled,
+		Source:            oldChartInfo.Source,
+		ExtraConfig:       newExtraConfig,
+		SeasonExtraConfig: oldChartInfo.SeasonExtraConfig,
+		StartYear:         oldChartInfo.StartYear,
+		Unit:              oldChartInfo.Unit,
+		UnitEn:            oldChartInfo.UnitEn,
+		ChartThemeId:      oldChartInfo.ChartThemeId,
+		SourcesFrom:       oldChartInfo.SourcesFrom,
+		Instructions:      oldChartInfo.Instructions,
+		MarkersLines:      oldChartInfo.MarkersLines,
+		MarkersAreas:      oldChartInfo.MarkersAreas,
+	}
+	newId, err := data_manage.AddChartInfo(chartInfo)
+	if err != nil {
+		err = fmt.Errorf("保存失败,Err:%s", err.Error())
+		return
+	}
+	chartInfo.ChartInfoId = int(newId)
+	edbInfoMappingList, err := data_manage.GetChartMappingList(oldChartInfo.ChartInfoId)
+	if err != nil {
+		err = fmt.Errorf("获取图表,指标信息失败,Err:" + err.Error())
+		return
+	}
+	// 添加图表与指标的关联关系
+	edbInfoIdArr := make([]int, 0)
+	{
+		mapList := make([]*data_manage.ChartEdbMapping, 0)
+		for _, v := range edbInfoMappingList {
+			edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+			timestamp = strconv.FormatInt(time.Now().UnixNano(), 10)
+			mapItem := &data_manage.ChartEdbMapping{
+				//ChartEdbMappingId: 0,
+				ChartInfoId:   chartInfo.ChartInfoId,
+				EdbInfoId:     v.EdbInfoId,
+				CreateTime:    time.Now(),
+				ModifyTime:    time.Now(),
+				UniqueCode:    utils.MD5(utils.CHART_PREFIX + "_" + timestamp),
+				MaxData:       v.MaxData,
+				MinData:       v.MinData,
+				IsOrder:       v.IsOrder,
+				IsAxis:        v.IsAxis,
+				EdbInfoType:   v.EdbInfoType,
+				LeadValue:     v.LeadValue,
+				LeadUnit:      v.LeadUnit,
+				ChartStyle:    v.ChartStyle,
+				ChartColor:    v.ChartColor,
+				ChartWidth:    v.ChartWidth,
+				Source:        v.Source,
+				EdbAliasName:  v.EdbAliasName,
+				IsConvert:     v.IsConvert,
+				ConvertType:   v.ConvertType,
+				ConvertValue:  v.ConvertValue,
+				ConvertUnit:   v.ConvertEnUnit,
+				ConvertEnUnit: v.ConvertEnUnit,
+			}
+			mapList = append(mapList, mapItem)
+		}
+		err = data_manage.AddChartEdbMapping(mapList)
+		if err != nil {
+			err = fmt.Errorf("保存失败,Err:%s", err.Error())
+			return
+		}
+	}
+	// 添加系列和图表映射
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	_, err = chartSeriesOb.GetItemByChartInfoId(oldChartInfo.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	} else {
+		// 新增指标系列
+		// 区间计算图表配置校验
+
+		err = AddSeries(edbInfoIdArr, chartInfo.ChartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS, extraConfig, chartInfo.ExtraConfig)
+		if err != nil {
+			errMsg = "操作失败"
+			err = errors.New("新增区间计算图表失败, Err: " + err.Error())
+			return
+		}
+	}
+
+	multipleGraphConfigChartMapping := &data_manage.MultipleGraphConfigChartMapping{
+		//Id:                    0,
+		MultipleGraphConfigId: extraConfig.MultipleGraphConfigId,
+		ChartInfoId:           chartInfo.ChartInfoId,
+		Source:                utils.CHART_SOURCE_RANGE_ANALYSIS,
+		ModifyTime:            time.Now(),
+		CreateTime:            time.Now(),
+	}
+	err = data_manage.AddMultipleGraphConfigChartMapping(multipleGraphConfigChartMapping)
+	if err != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增区间计算图表和配置关联关系失败, Err: " + err.Error())
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
+
+	return
+}
+
+/*
+// CalculateCorrelation 计算区间计算-获取x轴和y轴
+func CalculateCorrelation(leadValue int, leadUnit, frequencyA, frequencyB string, dataListA, dataListB []*data_manage.EdbDataList) (xEdbIdValue []int, yDataList []data_manage.YData, err error) {
+	xData := make([]int, 0)
+	yData := make([]float64, 0)
+	if leadValue == 0 {
+		xData = append(xData, 0)
+	}
+	if leadValue > 0 {
+		leadMin := 0 - leadValue
+		xLen := 2*leadValue + 1
+		for i := 0; i < xLen; i++ {
+			n := leadMin + i
+			xData = append(xData, n)
+		}
+	}
+
+	// 计算窗口,不包含第一天
+	//startDateTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+	//startDate = startDateTime.AddDate(0, 0, 1).Format(utils.FormatDate)
+
+	//// 2023-03-02 时间序列始终以指标B为基准, 始终是A进行平移
+	//baseEdbInfo := edbInfoMappingB
+	//changeEdbInfo := edbInfoMappingA
+	// 2023-03-17 时间序列始终以指标A为基准, 始终是B进行平移
+	//baseEdbInfo := edbInfoMappingA
+	//changeEdbInfo := edbInfoMappingB
+
+	// 获取时间基准指标在时间区间内的值
+	//aDataList := make([]*data_manage.EdbDataList, 0)
+	//switch baseEdbInfo.EdbInfoCategoryType {
+	//case 0:
+	//	aDataList, err = data_manage.GetEdbDataList(baseEdbInfo.Source, baseEdbInfo.SubSource, baseEdbInfo.EdbInfoId, startDate, endDate)
+	//case 1:
+	//	_, aDataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(baseEdbInfo.EdbInfoId, startDate, endDate, false)
+	//default:
+	//	err = errors.New("指标base类型异常")
+	//	return
+	//}
+	//
+	//// 获取变频指标所有日期的值, 插值法完善数据
+	//bDataList := make([]*data_manage.EdbDataList, 0)
+	//switch changeEdbInfo.EdbInfoCategoryType {
+	//case 0:
+	//	bDataList, err = data_manage.GetEdbDataList(changeEdbInfo.Source, changeEdbInfo.SubSource, changeEdbInfo.EdbInfoId, "", "")
+	//case 1:
+	//	_, bDataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(changeEdbInfo.EdbInfoId, "", "", false)
+	//default:
+	//	err = errors.New("指标change类型异常")
+	//	return
+	//}
+	//changeDataMap := make(map[string]float64)
+	//newChangeDataList, e := HandleDataByLinearRegression(bDataList, changeDataMap)
+	//if e != nil {
+	//	err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+	//	return
+	//}
+
+	// 2023-03-17 时间序列始终以指标A为基准, 始终是B进行平移
+	baseDataList := make([]*data_manage.EdbDataList, 0)
+	baseDataMap := make(map[string]float64)
+	changeDataList := make([]*data_manage.EdbDataList, 0)
+	changeDataMap := make(map[string]float64)
+
+	// 先把低频指标升频为高频
+	{
+		frequencyIntMap := map[string]int{
+			"日度": 1,
+			"周度": 2,
+			"旬度": 3,
+			"月度": 4,
+			"季度": 5,
+			"年度": 6,
+		}
+
+		// 如果A指标是高频,那么就需要对B指标进行升频
+		if frequencyIntMap[frequencyA] < frequencyIntMap[frequencyB] {
+			tmpNewChangeDataList, e := HandleDataByLinearRegression(dataListB, changeDataMap)
+			if e != nil {
+				err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+				return
+			}
+			changeDataList = tmpNewChangeDataList
+			baseDataList = dataListA
+			for _, v := range baseDataList {
+				baseDataMap[v.DataTime] = v.Value
+			}
+
+		} else if frequencyIntMap[frequencyA] > frequencyIntMap[frequencyB] {
+			// 如果B指标是高频,那么就需要对A指标进行升频
+			tmpNewChangeDataList, e := HandleDataByLinearRegression(dataListA, baseDataMap)
+			if e != nil {
+				err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+				return
+			}
+			baseDataList = tmpNewChangeDataList
+
+			changeDataList = dataListB
+			for _, v := range changeDataList {
+				changeDataMap[v.DataTime] = v.Value
+			}
+		} else {
+			baseDataList = dataListA
+			for _, v := range baseDataList {
+				baseDataMap[v.DataTime] = v.Value
+			}
+			changeDataList = dataListB
+			for _, v := range changeDataList {
+				changeDataMap[v.DataTime] = v.Value
+			}
+		}
+
+	}
+
+	// 计算不领先也不滞后时的相关系数
+	baseCalculateData := make([]float64, 0)
+	baseDataTimeArr := make([]string, 0)
+	for i := range baseDataList {
+		baseDataTimeArr = append(baseDataTimeArr, baseDataList[i].DataTime)
+		baseCalculateData = append(baseCalculateData, baseDataList[i].Value)
+	}
+
+	//zeroBaseData := make([]float64, 0)
+	//zeroCalculateData := make([]float64, 0)
+	//for i := range baseDataTimeArr {
+	//	tmpBaseVal, ok1 := baseDataMap[baseDataTimeArr[i]]
+	//	tmpCalculateVal, ok2 := changeDataMap[baseDataTimeArr[i]]
+	//	if ok1 && ok2 {
+	//		zeroBaseData = append(zeroBaseData, tmpBaseVal)
+	//		zeroCalculateData = append(zeroCalculateData, tmpCalculateVal)
+	//	}
+	//}
+	//if len(zeroBaseData) != len(zeroCalculateData) {
+	//	err = fmt.Errorf("相关系数两组序列元素数不一致, %d-%d", len(baseCalculateData), len(zeroCalculateData))
+	//	return
+	//}
+	//zeroRatio := utils.CalculateCorrelationByIntArr(zeroBaseData, zeroCalculateData)
+	//if leadValue == 0 {
+	//	yData = append(yData, zeroRatio)
+	//}
+
+	// 计算领先/滞后N期
+	if leadValue > 0 {
+		// 平移变频指标领先/滞后的日期(单位天)
+		moveUnitDays := utils.FrequencyDaysMap[leadUnit]
+
+		for i := range xData {
+			//if xData[i] == 0 {
+			//	yData = append(yData, zeroRatio)
+			//	continue
+			//}
+			xCalculateData := make([]float64, 0)
+			yCalculateData := make([]float64, 0)
+
+			// 平移指定天数
+			mDays := int(moveUnitDays) * xData[i]
+			_, dMap := MoveDataDaysToNewDataList(changeDataList, mDays)
+
+			// 取出对应的基准日期的值
+			for i2 := range baseDataTimeArr {
+				tmpDate := baseDataTimeArr[i2]
+				if yVal, ok := dMap[tmpDate]; ok {
+					xCalculateData = append(xCalculateData, baseCalculateData[i2])
+					yCalculateData = append(yCalculateData, yVal)
+				}
+			}
+			if len(yCalculateData) <= 0 {
+				//err = fmt.Errorf("领先滞后相关系数两组序列元素数不一致, %d-%d", len(baseCalculateData), len(yCalculateData))
+				//return
+				// 领先滞后后,没有可以计算的数据了
+				continue
+			}
+
+			// 公式计算出领先/滞后频度对应点的区间计算系数
+			ratio := utils.CalculateCorrelationByIntArr(xCalculateData, yCalculateData)
+			yData = append(yData, ratio)
+		}
+	}
+
+	xEdbIdValue = xData
+	yDataList = make([]data_manage.YData, 0)
+	yDate := "0000-00-00"
+	yDataList = append(yDataList, data_manage.YData{
+		Date:  yDate,
+		Value: yData,
+	})
+	return
+}
+
+// GetFactorChartDataByChartId 获取多因子区间计算图表数据
+func GetFactorChartDataByChartId(chartInfoId int, extraConfig string) (xEdbIdValue []int, yDataList []data_manage.YData, err error) {
+	if chartInfoId <= 0 {
+		return
+	}
+	// 指标对应的图例
+	extra := new(data_manage.CorrelationChartInfoExtraConfig)
+	if extraConfig != "" {
+		if e := json.Unmarshal([]byte(extraConfig), extra); e != nil {
+			err = fmt.Errorf("解析图表额外配置失败, err: %v", e)
+			return
+		}
+	}
+	legends := make(map[string]*data_manage.CorrelationChartLegend)
+	if extra != nil {
+		for _, v := range extra.LegendConfig {
+			s := fmt.Sprintf("%d-%d", v.SeriesId, v.EdbInfoId)
+			legends[s] = v
+		}
+	}
+
+	// 获取图表引用到的系列指标
+	chartMappingOb := new(data_manage.FactorEdbSeriesChartMapping)
+	cond := fmt.Sprintf(" AND %s = ? AND %s = 1", chartMappingOb.Cols().ChartInfoId, chartMappingOb.Cols().EdbUsed)
+	pars := make([]interface{}, 0)
+	pars = append(pars, chartInfoId)
+	chartMappings, e := chartMappingOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("获取图表引用系列指标失败")
+		return
+	}
+
+	// 取出计算结果
+	yDataList = make([]data_manage.YData, 0)
+	yDate := "0000-00-00"
+	for k, m := range chartMappings {
+		var values []data_manage.FactorEdbSeriesCorrelationMatrixValues
+		if m.CalculateData != "" {
+			e = json.Unmarshal([]byte(m.CalculateData), &values)
+			if e != nil {
+				err = fmt.Errorf("系列指标计算数据有误, err: %v", e)
+				return
+			}
+		}
+		var y []float64
+		for _, v := range values {
+			if k == 0 {
+				xEdbIdValue = append(xEdbIdValue, v.XData)
+			}
+			y = append(y, v.YData)
+		}
+		var yData data_manage.YData
+		yData.Date = yDate
+		yData.Value = y
+		yData.SeriesEdb.SeriesId = m.FactorEdbSeriesId
+		yData.SeriesEdb.EdbInfoId = m.EdbInfoId
+
+		// 图例
+		s := fmt.Sprintf("%d-%d", m.FactorEdbSeriesId, m.EdbInfoId)
+		legend := legends[s]
+		if legend != nil {
+			yData.Name = legend.LegendName
+			yData.Color = legend.Color
+		}
+		yDataList = append(yDataList, yData)
+	}
+	return
+}
+
+// FormatChartEdbInfoMappings 补充指标信息
+func FormatChartEdbInfoMappings(chartInfoId int, mappings []*data_manage.ChartEdbInfoMapping) (edbList []*data_manage.ChartEdbInfoMapping, err error) {
+	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
+	if len(mappings) == 0 {
+		return
+	}
+
+	for _, v := range mappings {
+		if chartInfoId <= 0 {
+			v.IsAxis = 1
+			v.LeadValue = 0
+			v.LeadUnit = ""
+			v.ChartEdbMappingId = 0
+			v.ChartInfoId = 0
+			v.IsOrder = false
+			v.EdbInfoType = 1
+			v.ChartStyle = ""
+			v.ChartColor = ""
+			v.ChartWidth = 0
+		} else {
+			v.LeadUnitEn = data.GetLeadUnitEn(v.LeadUnit)
+			v.LeadUnitEn = data.GetLeadUnitEn(v.LeadUnit)
+		}
+		v.FrequencyEn = data.GetFrequencyEn(v.Frequency)
+		if v.Unit == `无` {
+			v.Unit = ``
+		}
+		edbList = append(edbList, v)
+	}
+	return
+}*/
+
+func GetEdbDateByMoveForward(startDate string, moveForward int, edbDataList []*data_manage.EdbDataList) (date string) {
+	// 根据日期进行排序
+	index := 0
+	length := len(edbDataList)
+	for i := length - 1; i >= 0; i-- {
+		item := edbDataList[i]
+		if item.DataTime == startDate {
+			index += 1
+			continue
+		}
+		if index >= moveForward {
+			date = item.DataTime
+			break
+		}
+		if index > 0 {
+			index += 1
+			date = item.DataTime
+		}
+	}
+	return
+}
+
+// HandleEdbDateChange 处理日期变换
+func HandleEdbDateChange(date string, dateChange []*data_manage.EdbDataDateChangeConf) (newDate string, err error) {
+	newDate = date
+	if newDate != "" {
+		if len(dateChange) > 0 {
+			var dateTime time.Time
+			dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+			if err != nil {
+				err = fmt.Errorf("日期解析失败: %s", err.Error())
+				return
+			}
+			for _, v := range dateChange {
+				if v.ChangeType == 1 {
+					dateTime = dateTime.AddDate(v.Year, v.Month, v.Day)
+					newDate = dateTime.Format(utils.FormatDate)
+				} else if v.ChangeType == 2 {
+					newDate, err, _ = utils.HandleSystemAppointDateT(dateTime, v.FrequencyDay, v.Frequency)
+					if err != nil {
+						return
+					}
+					dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+					if err != nil {
+						err = fmt.Errorf("日期解析失败: %s", err.Error())
+						return
+					}
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// 添加指标系列和数据
+func AddSeries(edbInfoIds []int, chartInfoId, chartInfoSource int, extraConf data_manage.ChartRangeAnalysisExtraConf, calculatesJson string) (err error) {
+	edbArr, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("获取指标列表失败, Err: " + e.Error())
+		return
+	}
+	if len(edbArr) == 0 {
+		err = fmt.Errorf("获取指标列表失败, 指标不存在")
+		return
+	}
+	edbInfoType := edbArr[0].EdbInfoType
+	// 新增指标系列
+	seriesItem := new(data_manage.FactorEdbSeries)
+	seriesItem.SeriesName = extraConf.SeriesName
+	seriesItem.EdbInfoType = edbInfoType
+	seriesItem.CreateTime = time.Now().Local()
+	seriesItem.ModifyTime = time.Now().Local()
+	seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculating
+	seriesItem.CalculateStep = calculatesJson
+	mappings := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	for _, v := range edbArr {
+		mappings = append(mappings, &data_manage.FactorEdbSeriesMapping{
+			EdbInfoId:  v.EdbInfoId,
+			EdbCode:    v.EdbCode,
+			CreateTime: time.Now().Local(),
+			ModifyTime: time.Now().Local(),
+		})
+	}
+
+	seriesId, e := seriesItem.CreateSeriesAndMapping(seriesItem, mappings)
+	if e != nil {
+		err = fmt.Errorf("新增因子指标系列失败, Err: " + e.Error())
+		return
+	}
+
+	// 图表关联-此处添加的chart_info_id=0
+	seriesChartMapping := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesChartMapping.CalculateType = data_manage.FactorEdbSeriesChartCalculateTypeRange
+	//新增图表和指标的映射关系
+	seriesChartMapping.CalculateData = ""
+	seriesChartMapping.FactorEdbSeriesId = seriesId
+	seriesChartMapping.ChartInfoId = chartInfoId
+	seriesChartMapping.Source = chartInfoSource
+	seriesChartMapping.CreateTime = time.Now().Local()
+	seriesChartMapping.ModifyTime = time.Now().Local()
+
+	if e = seriesChartMapping.Create(); e != nil {
+		err = fmt.Errorf("新增图表关联失败, Err: " + e.Error())
+		return
+	}
+
+	// todo 计算指标数据并存储
+	_, e = FactorEdbStepCalculateRange(seriesId, edbInfoIds, extraConf, false)
+	if e != nil {
+		err = fmt.Errorf("计算因子指标失败, Err: " + e.Error())
+		return
+	}
+
+	// 更新系列计算状态
+	cols := []string{seriesItem.Cols().CalculateState, seriesItem.Cols().ModifyTime}
+	seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculated
+	seriesItem.ModifyTime = time.Now().Local()
+	if e = seriesItem.Update(cols); e != nil {
+		err = fmt.Errorf("更新因子指标系列计算状态失败, Err: " + e.Error())
+		return
+	}
+	return
+}
+
+func EditSeries(seriesMapping *data_manage.FactorEdbSeriesChartMapping, edbInfoIds []int, extraConf data_manage.ChartRangeAnalysisExtraConf, calculatesJson string, recalculate bool) (err error) {
+	seriesOb := new(data_manage.FactorEdbSeries)
+	seriesItem, e := seriesOb.GetItemById(seriesMapping.FactorEdbSeriesId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			err = fmt.Errorf("因子指标系列不存在, Err: " + e.Error())
+			return
+		}
+		err = fmt.Errorf("获取因子指标系列失败, Err: " + e.Error())
+		return
+	}
+
+	edbArr, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("获取指标列表失败, Err: " + e.Error())
+		return
+	}
+	if len(edbArr) == 0 {
+		err = fmt.Errorf("指标列表为空")
+		return
+	}
+	var calculateResp data_manage.FactorEdbSeriesStepCalculateResp
+	calculateResp.SeriesId = seriesItem.FactorEdbSeriesId
+
+	// 如果不需要进行重新计算(比如只改了系列名称)那么只更新指标系列
+	seriesItem.SeriesName = extraConf.SeriesName
+	seriesItem.EdbInfoType = edbArr[0].EdbInfoType
+	seriesItem.ModifyTime = time.Now().Local()
+	updateCols := []string{seriesOb.Cols().SeriesName, seriesOb.Cols().EdbInfoType, seriesOb.Cols().ModifyTime}
+	if !recalculate {
+		if e = seriesItem.Update(updateCols); e != nil {
+			err = fmt.Errorf("更新因子指标系列失败, Err: " + e.Error())
+			return
+		}
+		return
+	}
+
+	// 更新系列信息和指标关联
+	seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculating
+	seriesItem.CalculateStep = calculatesJson
+	updateCols = append(updateCols, seriesOb.Cols().CalculateState, seriesOb.Cols().CalculateStep)
+
+	mappings := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	for _, v := range edbArr {
+		mappings = append(mappings, &data_manage.FactorEdbSeriesMapping{
+			EdbInfoId:  v.EdbInfoId,
+			EdbCode:    v.EdbCode,
+			CreateTime: time.Now().Local(),
+			ModifyTime: time.Now().Local(),
+		})
+	}
+	if e = seriesItem.EditSeriesAndMapping(seriesItem, mappings, updateCols); e != nil {
+		err = fmt.Errorf("更新因子指标系列信息失败, Err: %s", e.Error())
+		return
+	}
+
+	// todo 重新计算
+	_, e = FactorEdbStepCalculateRange(seriesItem.FactorEdbSeriesId, edbInfoIds, extraConf, false)
+	if e != nil {
+		err = fmt.Errorf("计算因子指标失败, Err: " + e.Error())
+		return
+	}
+
+	// 更新系列计算状态
+	cols := []string{seriesItem.Cols().CalculateState, seriesItem.Cols().ModifyTime}
+	seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculated
+	seriesItem.ModifyTime = time.Now().Local()
+	if e = seriesItem.Update(cols); e != nil {
+		err = fmt.Errorf("更新因子指标系列计算状态失败, Err: %s", e.Error())
+		return
+	}
+	return
+}
+
+// FactorEdbStepCalculateRange 因子指标-区间计算
+func FactorEdbStepCalculateRange(seriesId int, edbArr []int, extraConf data_manage.ChartRangeAnalysisExtraConf, recalculate bool) (calculateResp data_manage.FactorEdbSeriesStepCalculateResp, err error) {
+	// todo 如果指标已保存,则用指标数据还是图表指标数据?
+	// 获取图表x轴y轴
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("StepCalculate计算失败, ErrMsg: %v", err)
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+		/*if len(calculateResp.Fail) > 0 {
+			tips := "StepCalculate计算失败, ErrMsg: "
+			for _, f := range calculateResp.Fail {
+				tips += fmt.Sprintf("code: %s, err: %s\n", f.EdbCode, f.ErrMsg)
+			}
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}*/
+	}()
+
+	edbInfoMappingList, e := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbArr)
+	if e != nil {
+		err = fmt.Errorf("获取区间计算图表, A指标mapping信息失败, Err:%v", e)
+		return
+	}
+
+	_, _, _, err = GetChartDataByEdbInfoList(0, 0, 0, "", "", edbInfoMappingList, &extraConf)
+	if err != nil {
+		err = fmt.Errorf("获取图表数据失败, ErrMsg: %v", err)
+		return
+	}
+
+	// 重新计算-先清除原数据
+	calculateDataOb := new(data_manage.FactorEdbSeriesCalculateDataQjjs)
+
+	cond := fmt.Sprintf("%s = ?", calculateDataOb.Cols().FactorEdbSeriesId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, seriesId)
+	if e := calculateDataOb.RemoveByCondition(cond, pars); e != nil {
+		err = fmt.Errorf("清除原数据失败, err: %v", e)
+		return
+	}
+
+	// 计算成功的保存结果
+	dataArr := make([]*data_manage.FactorEdbSeriesCalculateDataQjjs, 0)
+	for _, v := range edbInfoMappingList {
+		dataList := v.DataList.([]*data_manage.EdbDataList)
+		for _, dataItem := range dataList {
+			dataTime, _ := time.ParseInLocation(utils.FormatDate, dataItem.DataTime, time.Local)
+			dataArr = append(dataArr, &data_manage.FactorEdbSeriesCalculateDataQjjs{
+				FactorEdbSeriesId: seriesId,
+				EdbInfoId:         v.EdbInfoId,
+				EdbCode:           v.EdbCode,
+				DataTime:          dataTime,
+				Value:             dataItem.Value,
+				CreateTime:        time.Now().Local(),
+				ModifyTime:        time.Now().Local(),
+				DataTimestamp:     dataItem.DataTimestamp,
+			})
+		}
+	}
+	if len(dataArr) == 0 {
+		err = fmt.Errorf("计算结果无数据, seriesId: %d", seriesId)
+		return
+	}
+	if e = calculateDataOb.CreateMulti(dataArr); e != nil {
+		err = fmt.Errorf("保存计算结果失败, seriesId: %d, err: %v, ", seriesId, e)
+		return
+	}
+	return
+}
+
+func CheckChartRangeExtraConfig(extraConfig data_manage.ChartRangeAnalysisExtraConf) (err error, errMsg string, isSendEmail bool) {
+	extraConfig.SeriesName = strings.TrimSpace(extraConfig.SeriesName)
+	if extraConfig.SeriesName == "" && extraConfig.EdbInfoMode == 1 {
+		errMsg = "请输入指标系列名称"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	if extraConfig.CalculateType > 5 || extraConfig.CalculateType < 0 {
+		errMsg = "计算方式参数错误"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	switch extraConfig.DateRangeType {
+	case 0:
+	case 1:
+		if len(extraConfig.ManualDateConf) == 0 {
+			errMsg = "请选择时间区间"
+			err = errors.New(errMsg)
+			return
+		}
+		// 先按开始时间排序
+		sort.Sort(data_manage.ChartRangeAnalysisManualDateConfList(extraConfig.ManualDateConf))
+		// 校验日期
+		// 1.如果截止时间小于指标的截止日期,需要重置为指标的截止日期
+		// 2.时间区间不能重叠
+		for i := 1; i < len(extraConfig.ManualDateConf); i++ {
+			start1, e := time.Parse(utils.FormatDate, extraConfig.ManualDateConf[i-1].EndDate)
+			if e != nil {
+				err = e
+				errMsg = "截止日期格式有误"
+				return
+			}
+			start2, e := time.Parse(utils.FormatDate, extraConfig.ManualDateConf[i].EndDate)
+			if e != nil {
+				err = e
+				errMsg = "截止日期格式有误"
+				return
+			}
+
+			start3, e := time.Parse(utils.FormatDate, extraConfig.ManualDateConf[i].StartDate)
+			if e != nil {
+				err = e
+				errMsg = "截止日期格式有误"
+				return
+			}
+			// 如果当前区间的开始时间小于等于前一个区间的结束时间,则存在重叠
+			if !start2.After(start1) || start3.Before(start1) {
+				errMsg = "日期区间存在重叠"
+				return
+			}
+		}
+		//如果截止时间大于指标的截止日期,需要重置为指标的截止日期
+	case 2:
+		if extraConfig.YearDateConf.StartDay == "" || extraConfig.YearDateConf.EndDay == "" {
+			errMsg = "请选择时间区间"
+			return
+		}
+		if _, e := time.Parse(utils.FormatMonthDay, extraConfig.YearDateConf.StartDay); e != nil {
+			errMsg = "开始日期格式有误"
+			return
+		}
+		if _, e := time.Parse(utils.FormatMonthDay, extraConfig.YearDateConf.EndDay); e != nil {
+			errMsg = "结束日期格式有误"
+			return
+		}
+	}
+	return
+}

+ 49 - 2
services/excel/lucky_sheet.go

@@ -6,13 +6,15 @@ import (
 	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/tealeg/xlsx"
-	"github.com/xuri/excelize/v2"
+	"math"
 	"os"
 	"reflect"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/tealeg/xlsx"
+	"github.com/xuri/excelize/v2"
 )
 
 type LuckySheetDataBak struct {
@@ -1743,8 +1745,53 @@ func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hide
 					tmp.MergeCell.Column = cell.MerData.Mer.Col
 				}
 				if cell.ShowStyle != "" {
+					var styleConfig request.MixCellShowStyle
+					if err := json.Unmarshal([]byte(cell.ShowStyle), &styleConfig); err != nil {
+						utils.FileLog.Info("表格样式showStyle解析失败", err.Error())
+					}
 					showFormatValue := fmt.Sprintf("%v", cell.ShowFormatValue)
+					if styleConfig.BackgroundColor != "" {
+						tmp.Background = styleConfig.BackgroundColor
+					}
+					if styleConfig.Color != "" {
+						tmp.FontColor = styleConfig.Color
+					}
 					tmp.Monitor = showFormatValue
+					tmpShowValue, err := strconv.ParseFloat(cell.Value, 64)
+					if err == nil {
+						switch styleConfig.Last {
+						case "nt":
+							// 先进行数字的百分比计算,然后保留小数点位数
+							percent := tmpShowValue * 100
+							if styleConfig.Decimal == nil {
+								continue
+							}
+							factor := math.Pow(10, float64(*styleConfig.Decimal))
+							rounded := math.Round(percent*factor) / factor
+							tmp.Monitor = fmt.Sprintf("%g%%", rounded)
+						case "decimal":
+							// 先保留小数点位数,再进行百分比计算
+							if styleConfig.Decimal == nil {
+								continue
+							}
+							factor := math.Pow(10, float64(*styleConfig.Decimal))
+							rounded := math.Round(tmpShowValue*factor) / factor
+							if styleConfig.Nt == "percent" {
+								percent := rounded * 100
+								var precisionStr string
+								if *styleConfig.Decimal > 2 {
+									precision := *styleConfig.Decimal - 2
+									precisionStr = strconv.FormatFloat(rounded, 'f', precision, 64)
+								} else {
+									precisionStr = strconv.FormatFloat(math.Round(percent), 'f', -1, 64)
+								}
+								tmp.Monitor = fmt.Sprintf("%s%%", precisionStr)
+							} else {
+								tmp.Monitor = fmt.Sprintf("%g", rounded)
+							}
+						}
+					}
+
 				}
 				dataCol = append(dataCol, tmp)
 			}

+ 224 - 0
services/excel/lucky_sheet_table.go

@@ -1,8 +1,13 @@
 package excel
 
 import (
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/utils"
 	"fmt"
 	"sort"
+	"strconv"
+	"strings"
+	"time"
 )
 
 // HandleTableCell 前端d毛需要我根据合并单元格处理掉多余的单元格
@@ -99,3 +104,222 @@ func handleCellVal(tmpTableColData LuckySheetDataValue) (valueStr string) {
 	}
 	return
 }
+
+// HandleRuleToTableCell 根据管理规则渲染单元格数据
+func HandleRuleToTableCell(excelInfoId int, oldTableData TableData) (newTableData TableData, err error) {
+	newTableData = oldTableData
+	excelRuleMappingList, err := excel.GetExcelRuleMappingByExcelInfoId(excelInfoId)
+	if err != nil {
+		return
+	}
+	if len(excelRuleMappingList) == 0 {
+		return
+	}
+	tableDataList := oldTableData.TableDataList
+	excelRuleMap := make(map[int]*excel.ExcelInfoRuleMappingView)
+	for _, v := range excelRuleMappingList {
+		excelRuleMap[v.ExcelInfoRuleMappingId] = v
+	}
+	ruleScopeMap := generateRuleScopeIndexMap(excelRuleMappingList)
+	for row, scopeValues := range ruleScopeMap {
+		for col, ruleId := range scopeValues {
+			if v, ok := excelRuleMap[ruleId]; ok {
+				if len(tableDataList) > row && len(tableDataList[row]) > col {
+					// 符合管理规则要求,则进行字体和背景颜色的渲染
+					if checkCellRule(v, tableDataList[row][col].Monitor, tableDataList) {
+						tableDataList[row][col].Background = v.BackgroundColor
+						tableDataList[row][col].FontColor = v.FontColor
+					}
+				} else {
+					continue
+				}
+			} else {
+				continue
+			}
+		}
+	}
+	return
+}
+
+func getCellValueByType(value string, valueType int, tableDataList [][]LuckySheetDataValue) (float64, bool) {
+	if valueType == 2 {
+		coords := strings.Split(value, ",")
+		var coordIntArr []int
+		for _, v := range coords {
+			t, _ := strconv.Atoi(v)
+			coordIntArr = append(coordIntArr, t)
+		}
+		if len(coordIntArr) == 2 {
+			x, y := coordIntArr[0]-1, coordIntArr[1]-1
+			conditionValue, err := strconv.ParseFloat(tableDataList[y][x].Monitor, 64)
+			if err != nil {
+				return 0, false
+			}
+			return conditionValue, true
+		}
+	} else {
+		conditionValue, err := strconv.ParseFloat(value, 64)
+		if err != nil {
+			return 0, false
+		}
+		return conditionValue, true
+	}
+	return 0, false
+}
+
+func checkCellRule(ruleInfo *excel.ExcelInfoRuleMappingView, value string, tableDataList [][]LuckySheetDataValue) bool {
+	var tableValue float64
+	var tableTime time.Time
+	var err error
+
+	if ruleInfo.RuleType == 5 {
+		tableTime, err = time.ParseInLocation(utils.FormatDate, value, time.Local)
+		if err != nil {
+			return false
+		}
+	} else {
+		tableValue, err = strconv.ParseFloat(value, 64)
+		if err != nil {
+			return false
+		}
+	}
+	switch ruleInfo.RuleType {
+	// 大于
+	case 1:
+		conditionValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if tableValue > conditionValue {
+			return true
+		}
+	// 小于
+	case 2:
+		conditionValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if tableValue < conditionValue {
+			return true
+		}
+	// 介于
+	case 3:
+		leftcondValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		rightcondValue, ok := getCellValueByType(ruleInfo.RightValueBack, ruleInfo.RightValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if leftcondValue <= tableValue && tableValue <= rightcondValue {
+			return true
+		}
+	// 等于
+	case 4:
+		conditionValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if tableValue == conditionValue {
+			return true
+		}
+	// 发生日期
+	case 5:
+		// 发生日期
+		var dateStart, dataEnd time.Time
+		switch ruleInfo.LeftValueBack {
+		// 今天
+		case "today":
+			// 获得今天的零点零分零秒
+			dateStart = utils.Today()
+			if tableTime == dateStart {
+				return true
+			}
+		// 明天
+		case "tomorrow":
+			dateStart = utils.Tomorrow()
+			if tableTime == dateStart {
+				return true
+			}
+		// 最近7天
+		case "last7days":
+			dateStart, dataEnd = utils.Last7Days()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 上周
+		case "lastweek":
+			dateStart, dataEnd = utils.LastWeek()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 本周
+		case "thisweek":
+			dateStart, dataEnd = utils.ThisWeek()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 下周
+		case "nextweek":
+			dateStart, dataEnd = utils.NextWeek()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 上月
+		case "lastmonth":
+			dateStart, dataEnd = utils.LastMonth()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 本月
+		case "thismonth":
+			dateStart, dataEnd = utils.ThisMonth()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 下月
+		case "nextmonth":
+			dateStart, dataEnd = utils.NextMonth()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		default:
+			return false
+		}
+
+	}
+	return false
+}
+
+func generateRuleScopeIndexMap(items []*excel.ExcelInfoRuleMappingView) (ruleScopeMap map[int]map[int]int) {
+	ruleScopeMap = make(map[int]map[int]int)
+	for _, item := range items {
+		coords := strings.Split(item.ScopeCoord, ",")
+		var coordIntArr []int
+		for _, v := range coords {
+			t, _ := strconv.Atoi(v)
+			coordIntArr = append(coordIntArr, t)
+		}
+
+		if len(coords) == 4 {
+			xmin, ymin, xmax, ymax := coordIntArr[0]-1, coordIntArr[1]-1, coordIntArr[2]-1, coordIntArr[3]-1
+			for i := ymin; i <= ymax; i++ {
+				for j := xmin; j <= xmax; j++ {
+					if _, ok := ruleScopeMap[i]; !ok {
+						ruleScopeMap[i] = make(map[int]int)
+					}
+					ruleScopeMap[i][j] = item.ExcelInfoRuleMappingId
+				}
+			}
+		}
+		if len(coords) == 2 {
+			x, y := coordIntArr[0]-1, coordIntArr[1]-1
+			if _, ok := ruleScopeMap[y]; !ok {
+				ruleScopeMap[y] = make(map[int]int)
+			}
+			ruleScopeMap[y][x] = item.ExcelInfoRuleMappingId
+		}
+	}
+	return
+}

+ 88 - 0
services/file.go

@@ -1,14 +1,21 @@
 package services
 
 import (
+	"bytes"
 	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/pdfcpu/pdfcpu/pkg/api"
+	"github.com/pdfcpu/pdfcpu/pkg/font"
+	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
+	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
+	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
 	"io"
 	"mime/multipart"
 	"os"
 	"os/exec"
+	"strings"
 	"time"
 )
 
@@ -179,3 +186,84 @@ func GetResourceUrlBySvgImg(imgData string) (resourceUrl string, err error, errM
 
 	return
 }
+
+func GeneralWaterMarkPdf(pdfPath, waterMarkStr string) (f2 *bytes.Buffer, err error) {
+	onTop := true
+	update := false
+	//fontDir := `C:\Users\123\go\src\eta\eta_api\static`
+	fontDir := `static`
+	font.UserFontDir = fontDir
+
+	ttfList := fontDir + `/SimHei.ttf`
+	err = api.InstallFonts([]string{ttfList})
+	if err != nil {
+		fmt.Println("InstallFonts err:", err)
+	}
+
+	api.DisableConfigDir()
+
+	f1, err := os.Open(pdfPath)
+	if err != nil {
+		fmt.Println("文件不存在")
+		return
+	}
+	defer func() {
+		_ = f1.Close()
+	}()
+
+	f2 = &bytes.Buffer{}
+
+	//strList := []string{waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr}
+	strList := []string{waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr}
+	//strList := []string{waterMarkStr}
+	newWaterMarkStr := strings.Join(strList, "  ")
+	wmList := make([]*model.Watermark, 0)
+	for i := types.TopLeft; i <= 9; i++ {
+		wm, tmpErr := api.TextWatermark(newWaterMarkStr, "fo:SimHei, points:48, col: 0.75 0.75 0.75, rot:45, scale:1 abs, opacity:0.3", onTop, update, types.POINTS)
+		if tmpErr != nil {
+			fmt.Println("TextWatermark err:", tmpErr)
+			return
+		}
+		//wm.Pos = types.Center
+		wm.Pos = i
+		wmList = append(wmList, wm)
+	}
+
+	err = AddWatermarks(f1, f2, nil, wmList, nil)
+
+	return
+}
+func AddWatermarks(rs io.ReadSeeker, w io.Writer, selectedPages []string, wmList []*model.Watermark, conf *model.Configuration) error {
+	if rs == nil {
+		return errors.New("pdfcpu: AddWatermarks: missing rs")
+	}
+
+	if conf == nil {
+		conf = model.NewDefaultConfiguration()
+	}
+	conf.Cmd = model.ADDWATERMARKS
+	conf.OptimizeDuplicateContentStreams = false
+
+	if len(wmList) <= 0 {
+		return errors.New("pdfcpu: missing watermark configuration")
+	}
+
+	ctx, err := api.ReadValidateAndOptimize(rs, conf)
+	if err != nil {
+		return err
+	}
+
+	var pages types.IntSet
+	pages, err = api.PagesForPageSelection(ctx.PageCount, selectedPages, true, true)
+	if err != nil {
+		return err
+	}
+
+	for _, wm := range wmList {
+		if err = pdfcpu.AddWatermarks(ctx, pages, wm); err != nil {
+			return err
+		}
+	}
+
+	return api.Write(ctx, w, conf)
+}

+ 16 - 0
services/report_v2.go

@@ -1450,3 +1450,19 @@ func GetGeneralPdfUrl(reportCode, classifyFirstName string, reportLayout int8) (
 
 	return
 }
+
+func GetReportWaterMarkPdf(reportInfo *models.Report, sysUser *system.Admin) {
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "pdf/" + dateDir
+	filePath, err := utils.DownloadFile(reportInfo.DetailPdfUrl, uploadDir)
+	if err != nil {
+		return
+	}
+	// 移除临时Pdf文件
+	defer func() {
+		_ = os.Remove(filePath)
+	}()
+
+	waterMarkStr := fmt.Sprintf("%s - %s", sysUser.RealName, sysUser.Mobile)
+	GeneralWaterMarkPdf(filePath, waterMarkStr)
+}

+ 8 - 8
services/smart_report.go

@@ -136,7 +136,7 @@ func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
 	return
 }
 
-func ReportToPdf(reportUrl, filePath string) (err error) {
+func ReportToPdf(width int, reportUrl, filePath string) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch
@@ -151,7 +151,7 @@ async def main():
     })
     page = await browser.newPage()
     await page.setViewport({
-        'width': 1920,
+        'width': %d,
         'height': 1080,
     })
     await page.goto('%s', {
@@ -186,7 +186,7 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, reportUrl, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, filePath)
 	utils.FileLog.Info("pdf pyCode: \n" + pyCode)
 	cmd := exec.Command("python3", "-c", pyCode)
 	output, e := cmd.CombinedOutput()
@@ -329,7 +329,11 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 	pdfPath := `./static/` + reportCode + ".pdf"
 	jpegPath := `./static/` + reportCode + ".jpg"
 
-	err = ReportToPdf(reportUrl, pdfPath)
+	width := 1560
+	if reportType == 3 {
+		width = 800
+	}
+	err = ReportToPdf(width, reportUrl, pdfPath)
 	if err != nil {
 		utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
 		go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
@@ -389,10 +393,6 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 
 	time.Sleep(1 * time.Minute)
 
-	width := 1200
-	if reportType == 3 {
-		width = 800
-	}
 	err = ReportToJpeg(width, reportUrl, jpegPath)
 	if err != nil {
 		utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())

+ 2 - 1
static/ErrMsgConfig.json

@@ -57,5 +57,6 @@
   "当前指标已用作预测指标,不可删除": "The current metric is used as a forecasting indicator and cannot be deleted.",
   "当前指标已添加到跨品种分析,不可删除": "The current metric has been added to cross-commodity analysis and cannot be deleted.",
   "指标已删除,请刷新页面": "Metric deleted, please refresh the page.",
-  "删除失败": "Deletion failed."
+  "删除失败": "Deletion failed.",
+  "指标数据异常,请检查": "The metric data is abnormal, please check."
 }

BIN
static/SimHei.gob


+ 144 - 0
utils/common.go

@@ -2484,6 +2484,150 @@ func DateConvMysqlConvMongo(dateCon string) string {
 	return cond
 }
 
+// handleSystemAppointDateT
+// @Description: 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天)
+// @author: Roc
+// @datetime2023-10-27 09:31:35
+// @param Frequency string
+// @param Day string
+// @return date string
+// @return err error
+// @return errMsg string
+func HandleSystemAppointDateT(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
+	//currDate := time.Now()
+	switch frequency {
+	case "本周":
+		day := int(currDate.Weekday())
+		if day == 0 { // 周日
+			day = 7
+		}
+		num := 0
+		switch appointDay {
+		case "周一":
+			num = 1
+		case "周二":
+			num = 2
+		case "周三":
+			num = 3
+		case "周四":
+			num = 4
+		case "周五":
+			num = 5
+		case "周六":
+			num = 6
+		case "周日":
+			num = 7
+		}
+		day = num - day
+		date = currDate.AddDate(0, 0, day).Format(FormatDate)
+	case "本旬":
+		day := currDate.Day()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if day <= 10 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location())
+			} else if day <= 20 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 11, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 21, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if day <= 10 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 10, 0, 0, 0, 0, currDate.Location())
+			} else if day <= 20 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 20, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1)
+			}
+		}
+		date = tmpDate.Format(FormatDate)
+	case "本月":
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location())
+		case "最后一天":
+			tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1)
+		}
+		date = tmpDate.Format(FormatDate)
+	case "本季":
+		month := currDate.Month()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if month <= 3 {
+				tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 4, 1, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 9 {
+				tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 10, 1, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if month <= 3 {
+				tmpDate = time.Date(currDate.Year(), 3, 31, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 9 {
+				tmpDate = time.Date(currDate.Year(), 9, 30, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+			}
+		}
+		date = tmpDate.Format(FormatDate)
+	case "本半年":
+		month := currDate.Month()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+			}
+		}
+		date = tmpDate.Format(FormatDate)
+	case "本年":
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+		case "最后一天":
+			tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+		}
+		date = tmpDate.Format(FormatDate)
+	default:
+		errMsg = "错误的日期频度:" + frequency
+		err = errors.New(errMsg)
+		return
+	}
+
+	return
+}
+
+func CompareFloatByOpStrings(op string, a, b float64) bool {
+	switch op {
+	case "=":
+		return a == b
+	case ">":
+		return a > b
+	case ">=":
+		return a >= b
+	case "<=":
+		return a <= b
+	case "<":
+		return a < b
+	}
+	return false
+}
 func GetDuration(filePath string) (duration string, err error) {
 	// 构建 FFmpeg 命令,使用 ffprobe 来获取媒体文件信息
 	cmd := exec.Command("ffprobe", "-i", filePath, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0")

+ 3 - 0
utils/constants.go

@@ -173,6 +173,8 @@ const (
 	DATA_SOURCE_BLOOMBERG                            = 83       // bloomberg彭博数据
 	DATA_SOURCE_BUSINESS                             = 84       // 来源于自有数据
 	DATA_SOURCE_SCI99                                = 85       // 卓创资讯 -> 85
+	DATA_SOURCE_CALCULATE_RANGEANLYSIS               = 87       //区间计算->87
+	DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS       = 90       // 预测指标区间计算->90
 	DATA_SOURCE_SCI_HQ                               = 88       // 卓创红期->88
 	DATA_SOURCE_OILCHEM                              = 89       // 隆众资讯 -> 89
 )
@@ -284,6 +286,7 @@ const (
 	CHART_SOURCE_LINE_FEATURE_FREQUENCY          = 9  // 统计特征-频率分布图表
 	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
+	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
 )
 
 // 批量配置图表的位置来源

+ 74 - 0
utils/time.go

@@ -0,0 +1,74 @@
+package utils
+
+import "time"
+
+// 获取今天的日期(零点零分零秒)
+func Today() time.Time {
+	now := time.Now()
+	return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+}
+
+// 获取明天的日期(零点零分零秒)
+func Tomorrow() time.Time {
+	return Today().AddDate(0, 0, 1)
+}
+
+// 获取最近7天的日期范围
+func Last7Days() (time.Time, time.Time) {
+	today := Today()
+	return today.AddDate(0, 0, -6), today.Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+}
+
+// 获取上周的日期范围
+func LastWeek() (time.Time, time.Time) {
+	start := Today().AddDate(0, 0, -7)
+	end := start.AddDate(0, 0, 6).Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+	return start, end
+}
+
+// 获取本周的日期范围
+func ThisWeek() (time.Time, time.Time) {
+	start := Today().Round(0).Local().AddDate(0, 0, 0-int(time.Now().Weekday())+1) // 0是本周的第一天,+1是因为AddDate是相对于当前时间的
+	end := start.AddDate(0, 0, 6).Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+	return start, end
+}
+
+// 获取下周的日期范围
+func NextWeek() (time.Time, time.Time) {
+	_, thisEnd := ThisWeek()
+	start := thisEnd.AddDate(0, 0, 1)
+	end := start.AddDate(0, 0, 6).Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+	return start, end
+}
+
+// 获取上月的日期范围
+func LastMonth() (time.Time, time.Time) {
+	today := time.Now()
+	year, month, _ := today.Date()
+	start := time.Date(year, month-1, 1, 0, 0, 0, 0, today.Location())
+	end := time.Date(year, month, 0, 23, 59, 59, 999999999, today.Location())
+	return start, end
+}
+
+// 获取本月的日期范围
+func ThisMonth() (time.Time, time.Time) {
+	today := time.Now()
+	year, month, _ := today.Date()
+	start := time.Date(year, month, 1, 0, 0, 0, 0, today.Location())
+	end := time.Date(year, month+1, 0, 23, 59, 59, 999999999, today.Location())
+	return start, end
+}
+
+// 获取下月的日期范围
+func NextMonth() (time.Time, time.Time) {
+	now := time.Now()
+	year, month, _ := now.Date()
+	nextMonth := month + 1
+	if nextMonth > 12 {
+		nextMonth = 1
+		year++
+	}
+	startOfNextMonth := time.Date(year, nextMonth, 1, 0, 0, 0, 0, now.Location())
+	endOfNextMonth := startOfNextMonth.AddDate(0, 1, -1).Add(time.Hour*23 + time.Minute*59 + time.Second*59)
+	return startOfNextMonth, endOfNextMonth
+}