Browse Source

Merge branch 'master' into ETA_1.4.5

# Conflicts:
#	utils/common.go
zwxi 1 year ago
parent
commit
ab76c07f99
84 changed files with 8828 additions and 642 deletions
  1. 319 0
      controllers/ai/ai.go
  2. 4 0
      controllers/classify.go
  3. 9 56
      controllers/data_manage/chart_classify.go
  4. 12 0
      controllers/data_manage/chart_common.go
  5. 80 6
      controllers/data_manage/chart_framework.go
  6. 1 27
      controllers/data_manage/correlation/correlation_chart_classify.go
  7. 1558 0
      controllers/data_manage/cross_variety/chart_info.go
  8. 554 0
      controllers/data_manage/cross_variety/classify.go
  9. 408 0
      controllers/data_manage/cross_variety/tag.go
  10. 295 0
      controllers/data_manage/cross_variety/variety.go
  11. 12 3
      controllers/data_manage/edb_info.go
  12. 18 6
      controllers/data_manage/edb_info_calculate.go
  13. 161 2
      controllers/data_manage/excel/excel_info.go
  14. 2 28
      controllers/data_manage/future_good/future_good_chart_classify.go
  15. 1 27
      controllers/data_manage/line_equation/line_chart_classify.go
  16. 1 27
      controllers/data_manage/line_feature/classify.go
  17. 54 14
      controllers/data_manage/my_chart.go
  18. 34 38
      controllers/data_manage/predict_edb_info.go
  19. 443 0
      controllers/data_manage/yongyi_data.go
  20. 103 10
      controllers/ppt_english.go
  21. 105 7
      controllers/ppt_v2.go
  22. 72 40
      controllers/sandbox/sandbox.go
  23. 14 3
      controllers/smart_report/smart_report.go
  24. 307 0
      controllers/smart_report/smart_resource.go
  25. 146 0
      models/aimod/ai.go
  26. 146 0
      models/data_manage/base_from_yongyi.go
  27. 223 0
      models/data_manage/base_from_yongyi_classify.go
  28. 23 0
      models/data_manage/chart_classify.go
  29. 101 0
      models/data_manage/chart_edb_mapping.go
  30. 16 13
      models/data_manage/chart_framework.go
  31. 6 1
      models/data_manage/chart_framework_node.go
  32. 1 1
      models/data_manage/chart_info.go
  33. 264 0
      models/data_manage/cross_variety/chart_info_cross_variety.go
  34. 148 0
      models/data_manage/cross_variety/chart_tag.go
  35. 260 0
      models/data_manage/cross_variety/chart_tag_variety.go
  36. 121 0
      models/data_manage/cross_variety/chart_variety.go
  37. 67 0
      models/data_manage/cross_variety/chart_variety_mapping.go
  38. 80 0
      models/data_manage/cross_variety/request/chart.go
  39. 31 0
      models/data_manage/cross_variety/request/tag.go
  40. 17 0
      models/data_manage/cross_variety/request/variety.go
  41. 21 0
      models/data_manage/cross_variety/response/chart.go
  42. 26 0
      models/data_manage/cross_variety/response/tag.go
  43. 19 0
      models/data_manage/cross_variety/response/variety.go
  44. 24 0
      models/data_manage/edb_data_base.go
  45. 19 6
      models/data_manage/edb_info.go
  46. 6 0
      models/data_manage/edb_info_calculate.go
  47. 1 0
      models/data_manage/edb_source.go
  48. 1 1
      models/data_manage/excel/excel_edb_mapping.go
  49. 16 0
      models/data_manage/excel/excel_info.go
  50. 59 2
      models/data_manage/my_chart.go
  51. 4 0
      models/data_manage/predict_edb_conf.go
  52. 2 0
      models/data_manage/predict_edb_info_calculate.go
  53. 2 0
      models/data_manage/request/predict_edb_info.go
  54. 1 1
      models/data_manage/response/predit_edb_info.go
  55. 36 0
      models/db.go
  56. 16 3
      models/ppt_english/ppt_english.go
  57. 15 2
      models/ppt_v2.go
  58. 1 0
      models/sandbox/request/sandbox.go
  59. 1 0
      models/sandbox/sandbox.go
  60. 35 4
      models/sandbox/sandbox_classify.go
  61. 18 0
      models/smart_report/smart_report.go
  62. 107 0
      models/smart_report/smart_resource.go
  63. 423 0
      routers/commentsRouter.go
  64. 16 0
      routers/router.go
  65. 69 0
      services/aiser/ai.go
  66. 22 0
      services/data/base_edb_lib.go
  67. 26 0
      services/data/chart_classify.go
  68. 2 2
      services/data/chart_info.go
  69. 1 1
      services/data/correlation/chart_info.go
  70. 962 0
      services/data/cross_variety/chart.go
  71. 80 1
      services/data/edb_classify.go
  72. 80 8
      services/data/edb_info.go
  73. 153 102
      services/data/edb_info_calculate.go
  74. 8 0
      services/data/excel/custom_analysis_edb.go
  75. 91 1
      services/data/excel/excel_info.go
  76. 74 32
      services/data/excel/mixed_table.go
  77. 1 1
      services/data/future_good/chart_info.go
  78. 43 0
      services/data/my_chart.go
  79. 2 156
      services/data/predict_edb_info.go
  80. 60 0
      services/ppt.go
  81. 3 3
      services/sandbox/sandbox.go
  82. 37 0
      utils/common.go
  83. 18 0
      utils/config.go
  84. 10 7
      utils/constants.go

+ 319 - 0
controllers/ai/ai.go

@@ -0,0 +1,319 @@
+package ai
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/aimod"
+	"eta/eta_api/services/aiser"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// AI
+type AiController struct {
+	controllers.BaseAuthController
+}
+
+// @Title 聊天接口
+// @Description 聊天接口
+// @Param	request	body aimod.ChatReq true "type json string"
+// @Success 200 {object} response.ListResp
+// @router /chat [post]
+func (this *AiController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req aimod.ChatReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.Ask == "" {
+		br.Msg = "请输入提问内容!"
+		br.ErrMsg = "请输入提问内容"
+		return
+	}
+
+	if utils.Re == nil {
+		key := "CACHE_CHAT_" + strconv.Itoa(this.SysUser.AdminId)
+		cacheVal, err := utils.Rc.RedisInt(key)
+		fmt.Println("RedisString:", cacheVal, "err:", err)
+		if err != nil && err.Error() != "redigo: nil returned" {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		putVal := 0
+		if cacheVal <= 0 {
+			putVal = utils.AiChatLimit
+		} else {
+			putVal = cacheVal - 1
+		}
+
+		if putVal <= 0 {
+			br.Msg = "您今日50次问答已达上限,请明天再来!"
+			br.ErrMsg = "您今日50次问答已达上限,请明天再来!"
+			return
+		}
+		lastSecond := utils.GetTodayLastSecond()
+		utils.Rc.Put(key, putVal, lastSecond)
+	}
+
+	//根据提问,获取信息
+	askUuid := utils.MD5(req.Ask)
+	chatMode, err := aimod.GetAiChatByAsk(askUuid)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取数据失败!"
+		br.ErrMsg = "获取数据失败,GetAiChatByAsk,Err:" + err.Error()
+		return
+	}
+	resp := new(aimod.ChatResp)
+	var answer string
+	//answerArr := []string{
+	//	"周度数据显示,成品油现货市场价格跟随原油下跌,但近期相对抗跌,裂解价差走扩。批零价差方面汽油收窄,柴油走扩",
+	//	"出口利润在原油下跌海外成品油矛盾更大的情况下汽柴油出口窗口完全关闭",
+	//	"汽油需求在经历五一假期的一段高峰后将回归平稳,总体没有明显矛盾,后期我们担心更多的还是柴油。"}
+	if chatMode != nil && chatMode.Answer != "" {
+		answer = chatMode.Answer
+	} else {
+		answer, err = aiser.ChatAutoMsg(req.Ask)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "获取数据失败,ChatAutoMsg,Err:" + err.Error()
+			return
+		}
+	}
+	resp.Ask = req.Ask
+	resp.Answer = answer
+
+	if req.AiChatTopicId <= 0 { //新增
+		topic := new(aimod.AiChatTopic)
+		topic.TopicName = req.Ask
+		topic.SysUserId = this.SysUser.AdminId
+		topic.SysUserRealName = this.SysUser.RealName
+		topic.CreateTime = time.Now()
+		topic.ModifyTime = time.Now()
+		topicId, err := aimod.AddAiChatTopic(topic)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "生成话题失败,Err:" + err.Error()
+			return
+		}
+		resp.AiChatTopicId = int(topicId)
+		chatItem := new(aimod.AiChat)
+		chatItem.AiChatTopicId = resp.AiChatTopicId
+		chatItem.Ask = req.Ask
+		chatItem.AskUuid = utils.MD5(req.Ask)
+		chatItem.Answer = answer
+		chatItem.Model = "gpt-4-1106-preview"
+		chatItem.SysUserId = this.SysUser.AdminId
+		chatItem.SysUserRealName = this.SysUser.RealName
+		chatItem.CreateTime = time.Now()
+		chatItem.ModifyTime = time.Now()
+		_, err = aimod.AddAiChat(chatItem)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		resp.AiChatTopicId = req.AiChatTopicId
+		chatItem := new(aimod.AiChat)
+		chatItem.AiChatTopicId = resp.AiChatTopicId
+		chatItem.Ask = req.Ask
+		chatItem.AskUuid = utils.MD5(req.Ask)
+		chatItem.Answer = answer
+		chatItem.Model = "gpt-4-1106-preview"
+		chatItem.SysUserId = this.SysUser.AdminId
+		chatItem.SysUserRealName = this.SysUser.RealName
+		chatItem.CreateTime = time.Now()
+		chatItem.ModifyTime = time.Now()
+		_, err = aimod.AddAiChat(chatItem)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+			return
+		}
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 获取话题列表
+// @Description 获取话题列表接口
+// @Success 200 {object} aimod.AiChatTopicListResp
+// @router /topic/list [get]
+func (this *AiController) TopicList() {
+	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
+	}
+	list, err := aimod.GetAiChatTopicList(sysUser.AdminId)
+	if err != nil {
+		br.Msg = "获取数据失败!"
+		br.ErrMsg = "获取主题记录信息失败,Err:" + err.Error()
+		return
+	}
+	resp := new(aimod.AiChatTopicListResp)
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 获取话题详情
+// @Description 获取话题详情接口
+// @Param   AiChatTopicId   query   int  true       "主题id"
+// @Success 200 {object} aimod.AiChatDetailResp
+// @router /topic/detail [get]
+func (this *AiController) TopicDetail() {
+	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
+	}
+
+	aiChatTopicId, _ := this.GetInt("AiChatTopicId")
+	list, err := aimod.GetAiChatList(aiChatTopicId)
+	if err != nil {
+		br.Msg = "获取数据失败!"
+		br.ErrMsg = "获取主题记录信息失败,Err:" + err.Error()
+		return
+	}
+	resp := new(aimod.AiChatDetailResp)
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 删除话题接口
+// @Description 删除话题接口
+// @Param	request	body aimod.TopicDeleteReq true "type json string"
+// @Success Ret=200 删除成功
+// @router /topic/delete [post]
+func (this *AiController) TopicDelete() {
+	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 aimod.TopicDeleteReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.AiChatTopicId <= 0 {
+		br.Msg = "参数错误!"
+		br.ErrMsg = "参数错误!AiChatTopicId:" + strconv.Itoa(req.AiChatTopicId)
+		return
+	}
+	err = aimod.DeleteTopic(req.AiChatTopicId)
+	if err != nil {
+		br.Msg = "删除失败!"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+	br.IsAddLog = true
+}
+
+// @Title 编辑话题接口
+// @Description 编辑话题接口
+// @Param	request	body aimod.TopicEditReq true "type json string"
+// @Success Ret=200 编辑成功
+// @router /topic/edit [post]
+func (this *AiController) TopicEdit() {
+	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 aimod.TopicEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.AiChatTopicId <= 0 {
+		br.Msg = "参数错误!"
+		br.ErrMsg = "参数错误!AiChatTopicId:" + strconv.Itoa(req.AiChatTopicId)
+		return
+	}
+	topic, err := aimod.GetAiChatTopicByTopicName(req.TopicName)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "编辑失败!"
+		br.ErrMsg = "获取数据失败!Err:" + err.Error()
+		return
+	}
+	if topic != nil && topic.AiChatTopicId != req.AiChatTopicId {
+		br.Msg = "话题名称已存在,请重新修改!"
+		return
+	}
+
+	err = aimod.EditTopic(req.AiChatTopicId, req.TopicName)
+	if err != nil {
+		br.Msg = "编辑失败!"
+		br.ErrMsg = "编辑失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+	br.IsAddLog = true
+}

+ 4 - 0
controllers/classify.go

@@ -669,6 +669,10 @@ func (this *ClassifyController) ListClassify() {
 	keyWord := this.GetString("KeyWord")
 	companyType := this.GetString("CompanyType")
 	hideDayWeek, _ := this.GetInt("HideDayWeek")
+	// 商家不隐藏晨周报
+	if utils.BusinessCode != utils.BusinessCodeRelease {
+		hideDayWeek = 0
+	}
 
 	var startSize int
 	if pageSize <= 0 {

+ 9 - 56
controllers/data_manage/chart_classify.go

@@ -95,7 +95,7 @@ func (this *ChartClassifyController) ChartClassifyListV2() {
 			return
 		}
 		// 移除没有权限的图表
-		allNodes := handleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
+		allNodes := data.HandleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
 		resp.AllNodes = allNodes
 
 		br.Ret = 200
@@ -110,11 +110,11 @@ func (this *ChartClassifyController) ChartClassifyListV2() {
 	key := utils.CACHE_CHART_CLASSIFY
 	if utils.Re == nil {
 		if utils.Re == nil && utils.Rc.IsExist(key) {
-			if data, err1 := utils.Rc.RedisBytes(key); err1 == nil {
-				err := json.Unmarshal(data, &resp)
+			if redisData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
+				err := json.Unmarshal(redisData, &resp)
 				if err == nil && resp != nil {
 					// 移除没有权限的图表
-					allNodes := handleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
+					allNodes := data.HandleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
 					resp.AllNodes = allNodes
 
 					br.Ret = 200
@@ -185,12 +185,12 @@ func (this *ChartClassifyController) ChartClassifyListV2() {
 
 	// 将数据加入缓存
 	if utils.Re == nil {
-		data, _ := json.Marshal(resp)
-		utils.Rc.Put(key, data, 2*time.Hour)
+		redisData, _ := json.Marshal(resp)
+		utils.Rc.Put(key, redisData, 2*time.Hour)
 	}
 
 	// 移除没有权限的图表
-	allNodes := handleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
+	allNodes := data.HandleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
 	resp.AllNodes = allNodes
 
 	br.Ret = 200
@@ -248,53 +248,6 @@ func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartCl
 	return
 }
 
-// handleNoPermissionChart 图表列表返回,将没有权限的图表移除
-func handleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*data_manage.ChartClassifyItems) {
-	// 移除没有权限的图表
-	newAllNodes = make([]*data_manage.ChartClassifyItems, 0)
-	for _, node := range allNodes {
-		// 二级分类
-		tmpNodeInfo := *node
-		tmpNodeList := make([]*data_manage.ChartClassifyItems, 0)
-		if node.Children != nil {
-			for _, chartList := range node.Children {
-				tmpInfo := *chartList
-				tmpList := make([]*data_manage.ChartClassifyItems, 0)
-
-				if chartList.Children != nil {
-					for _, chartInfo := range chartList.Children {
-						thirdInfo := *chartInfo
-						thirdList := make([]*data_manage.ChartClassifyItems, 0)
-						// 如果指标不可见,那么就不返回该指标
-						if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
-							continue
-						}
-						tmpList = append(tmpList, chartInfo)
-
-						if chartInfo.Children != nil {
-							for _, thirdChart := range chartInfo.Children {
-								// 如果指标不可见,那么就不返回该指标
-								if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
-									continue
-								}
-								thirdList = append(thirdList, thirdChart)
-							}
-						}
-						thirdInfo.Children = thirdList
-						tmpList = append(tmpList, &thirdInfo)
-					}
-				}
-				tmpInfo.Children = tmpList
-				tmpNodeList = append(tmpNodeList, &tmpInfo)
-			}
-		}
-		tmpNodeInfo.Children = tmpNodeList
-		newAllNodes = append(newAllNodes, &tmpNodeInfo)
-	}
-
-	return
-}
-
 // ChartClassifyItems
 // @Title 获取所有图表分类接口-不包含图表
 // @Description 获取所有图表分类接口-不包含图表
@@ -1015,7 +968,7 @@ func (this *ChartClassifyController) ChartClassifyChartListV2() {
 			return
 		}
 		// 移除没有权限的图表
-		allNodes := handleNoPermissionChart(allChartInfo, noPermissionChartIdMap)
+		allNodes := data.HandleNoPermissionChart(allChartInfo, noPermissionChartIdMap)
 		resp.AllNodes = allNodes
 
 		br.Ret = 200
@@ -1033,7 +986,7 @@ func (this *ChartClassifyController) ChartClassifyChartListV2() {
 		return
 	}
 	// 移除没有权限的图表
-	allNodes := handleNoPermissionChart(allChartInfo, noPermissionChartIdMap)
+	allNodes := data.HandleNoPermissionChart(allChartInfo, noPermissionChartIdMap)
 
 	for k, item := range allNodes {
 		item.Button = data.GetChartOpButton(this.SysUser, item.SysUserId)

+ 12 - 0
controllers/data_manage/chart_common.go

@@ -2,6 +2,7 @@ package data_manage
 
 import (
 	"eta/eta_api/controllers/data_manage/correlation"
+	"eta/eta_api/controllers/data_manage/cross_variety"
 	"eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
@@ -138,6 +139,17 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_CROSS_HEDGING:
+		resp, isOk, msg, errMsg := cross_variety.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 = "错误的图表"

+ 80 - 6
controllers/data_manage/chart_framework.go

@@ -5,6 +5,8 @@ import (
 	"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/utils"
 	"fmt"
 	"strings"
@@ -75,7 +77,7 @@ func (this *ChartFrameworkController) List() {
 	}
 	resp := make([]*data_manage.ChartFrameworkItem, 0)
 	for _, v := range list {
-		t := data_manage.FormatChartFramework2Item(v)
+		t := data_manage.FormatChartFramework2Item(v, make([]*data_manage.ChartFrameworkNodeItem, 0))
 		resp = append(resp, t)
 	}
 
@@ -119,10 +121,25 @@ func (this *ChartFrameworkController) PublicMenu() {
 		return
 	}
 
+	// 用户被删除不展示框架
+	admins, e := system.GetSysAdminList(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取系统用户列表失败, Err: " + e.Error()
+		return
+	}
+	adminExist := make(map[int]bool)
+	for _, v := range admins {
+		adminExist[v.AdminId] = true
+	}
+
 	userExist := make(map[int]bool)
 	userFrameworks := make(map[int][]*data_manage.ChartFrameworkItem)
 	resp := make([]*data_manage.ChartFrameworkPublicMenuItem, 0)
 	for _, v := range list {
+		if !adminExist[v.AdminId] {
+			continue
+		}
 		if !userExist[v.AdminId] {
 			u := new(data_manage.ChartFrameworkPublicMenuItem)
 			u.AdminId = v.AdminId
@@ -130,7 +147,7 @@ func (this *ChartFrameworkController) PublicMenu() {
 			resp = append(resp, u)
 			userExist[v.AdminId] = true
 		}
-		t := data_manage.FormatChartFramework2Item(v)
+		t := data_manage.FormatChartFramework2Item(v, make([]*data_manage.ChartFrameworkNodeItem, 0))
 		if userFrameworks[v.AdminId] == nil {
 			userFrameworks[v.AdminId] = make([]*data_manage.ChartFrameworkItem, 0)
 		}
@@ -199,6 +216,14 @@ func (this *ChartFrameworkController) Add() {
 		}
 	}
 
+	// 获取图表分类下各自的图表数
+	chartsNumMap, e := data.GetMyChartClassifyIdNumMap(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败, GetMyChartClassifyIdNumMap Err: " + e.Error()
+		return
+	}
+
 	now := time.Now().Local()
 	frameworkCode := utils.MD5(fmt.Sprint(now.UnixMilli()))
 	item := new(data_manage.ChartFramework)
@@ -211,6 +236,7 @@ func (this *ChartFrameworkController) Add() {
 	item.CreateTime = now
 	item.ModifyTime = now
 	nodes := make([]*data_manage.ChartFrameworkNode, 0)
+	itemNodes := make([]*data_manage.ChartFrameworkNodeItem, 0)
 	if len(req.Nodes) > 0 {
 		for _, v := range req.Nodes {
 			if v.MyChartClassifyId <= 0 {
@@ -218,10 +244,15 @@ func (this *ChartFrameworkController) Add() {
 			}
 			t := new(data_manage.ChartFrameworkNode)
 			t.FrameworkName = req.FrameworkName
+			t.NodeId = v.NodeId
 			t.NodeName = v.NodeName
 			t.MyChartClassifyId = v.MyChartClassifyId
 			t.CreateTime = now
 			nodes = append(nodes, t)
+
+			// 响应节点数据
+			td := data_manage.FormatChartFrameworkNode2Item(t, chartsNumMap[t.MyChartClassifyId])
+			itemNodes = append(itemNodes, td)
 		}
 	}
 	if e := item.CreateFrameworkAndNodes(item, nodes); e != nil {
@@ -229,7 +260,7 @@ func (this *ChartFrameworkController) Add() {
 		br.ErrMsg = "新增框架及节点失败, Err: " + e.Error()
 		return
 	}
-	detail := data_manage.FormatChartFramework2Item(item)
+	detail := data_manage.FormatChartFramework2Item(item, itemNodes)
 
 	br.Data = detail
 	br.Ret = 200
@@ -308,6 +339,14 @@ func (this *ChartFrameworkController) Edit() {
 		}
 	}
 
+	// 获取图表分类下各自的图表数
+	chartsNumMap, e := data.GetMyChartClassifyIdNumMap(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败, GetMyChartClassifyIdNumMap Err: " + e.Error()
+		return
+	}
+
 	now := time.Now().Local()
 	item.FrameworkName = req.FrameworkName
 	item.FrameworkImg = req.FrameworkImg
@@ -315,6 +354,7 @@ func (this *ChartFrameworkController) Edit() {
 	item.ModifyTime = now
 	updateCols := []string{"FrameworkName", "FrameworkImg", "FrameworkContent", "ModifyTime"}
 	nodes := make([]*data_manage.ChartFrameworkNode, 0)
+	itemNodes := make([]*data_manage.ChartFrameworkNodeItem, 0)
 	if len(req.Nodes) > 0 {
 		for _, v := range req.Nodes {
 			if v.MyChartClassifyId <= 0 {
@@ -323,10 +363,15 @@ func (this *ChartFrameworkController) Edit() {
 			t := new(data_manage.ChartFrameworkNode)
 			t.ChartFrameworkId = req.ChartFrameworkId
 			t.FrameworkName = req.FrameworkName
+			t.NodeId = v.NodeId
 			t.NodeName = v.NodeName
 			t.MyChartClassifyId = v.MyChartClassifyId
 			t.CreateTime = now
 			nodes = append(nodes, t)
+
+			// 响应节点数据
+			td := data_manage.FormatChartFrameworkNode2Item(t, chartsNumMap[t.MyChartClassifyId])
+			itemNodes = append(itemNodes, td)
 		}
 	}
 	if e := item.EditFrameworkAndNodes(item, updateCols, nodes); e != nil {
@@ -334,7 +379,7 @@ func (this *ChartFrameworkController) Edit() {
 		br.ErrMsg = "编辑框架及节点失败, Err: " + e.Error()
 		return
 	}
-	detail := data_manage.FormatChartFramework2Item(item)
+	detail := data_manage.FormatChartFramework2Item(item, itemNodes)
 
 	br.Data = detail
 	br.Ret = 200
@@ -715,11 +760,40 @@ func (this *ChartFrameworkController) Detail() {
 			br.Msg = "框架不存在, 请刷新页面"
 			return
 		}
-		br.Msg = "操作失败"
+		br.Msg = "获取失败"
 		br.ErrMsg = "获取框架失败, Err: " + e.Error()
 		return
 	}
-	detail := data_manage.FormatChartFramework2Item(item)
+
+	// 获取节点
+	nodeOb := new(data_manage.ChartFrameworkNode)
+	nodeCond := ` AND chart_framework_id = ?`
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, frameworkId)
+	nodes, e := nodeOb.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取框架节点失败, Err: " + e.Error()
+		return
+	}
+
+	// 获取图表分类下各自的图表数
+	chartsNumMap, e := data.GetMyChartClassifyIdNumMap(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败, GetMyChartClassifyIdNumMap Err: " + e.Error()
+		return
+	}
+
+	// 格式化响应数据
+	itemNodes := make([]*data_manage.ChartFrameworkNodeItem, 0)
+	for _, v := range nodes {
+		if v.NodeId == "" {
+			continue
+		}
+		itemNodes = append(itemNodes, data_manage.FormatChartFrameworkNode2Item(v, chartsNumMap[v.MyChartClassifyId]))
+	}
+	detail := data_manage.FormatChartFramework2Item(item, itemNodes)
 
 	br.Data = detail
 	br.Ret = 200

+ 1 - 27
controllers/data_manage/correlation/correlation_chart_classify.go

@@ -104,7 +104,7 @@ func (this *CorrelationChartClassifyController) ChartClassifyList() {
 	}
 
 	// 移除没有权限的图表
-	allNodes := handleNoPermissionChart(rootList, noPermissionChartIdMap)
+	allNodes := data.HandleNoPermissionChart(rootList, noPermissionChartIdMap)
 	resp.AllNodes = allNodes
 
 	br.Ret = 200
@@ -162,32 +162,6 @@ func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartCl
 	return
 }
 
-// handleNoPermissionChart 图表列表返回,将没有权限的图表移除
-func handleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*data_manage.ChartClassifyItems) {
-	// 移除没有权限的图表
-	newAllNodes = make([]*data_manage.ChartClassifyItems, 0)
-	for _, node := range allNodes {
-		// 二级分类
-		tmpNodeInfo := *node
-		tmpNodeList := make([]*data_manage.ChartClassifyItems, 0)
-
-		if node.Children != nil {
-			for _, chartInfo := range node.Children {
-				// 如果指标不可见,那么就不返回该指标
-				if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
-					continue
-				}
-				tmpNodeList = append(tmpNodeList, chartInfo)
-			}
-		}
-
-		tmpNodeInfo.Children = tmpNodeList
-		newAllNodes = append(newAllNodes, &tmpNodeInfo)
-	}
-
-	return
-}
-
 // ChartClassifyItems
 // @Title 获取所有相关性图表分类接口-不包含图表
 // @Description 获取所有相关性图表分类接口-不包含图表

+ 1558 - 0
controllers/data_manage/cross_variety/chart_info.go

@@ -0,0 +1,1558 @@
+package cross_variety
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	cross_varietyModels "eta/eta_api/models/data_manage/cross_variety"
+	"eta/eta_api/models/data_manage/cross_variety/request"
+	"eta/eta_api/models/data_manage/cross_variety/response"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/cross_variety"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ChartInfoController
+// @Description: 跨品种分析图表
+type ChartInfoController struct {
+	controllers.BaseAuthController
+}
+
+// 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       "图表类型,10:跨品种分析
+// @Success 200 {object} data_manage.ChartListResp
+// @router /chart_info/list [get]
+func (c *ChartInfoController) List() {
+	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
+	}
+
+	chartClassifyId, _ := c.GetInt("ChartClassifyId")
+
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyword := c.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, _ := c.GetInt("Source")
+	if source <= 0 {
+		source = utils.CHART_SOURCE_CROSS_HEDGING
+	}
+
+	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 != "" {
+		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+	}
+
+	//只看我的
+	isShowMe, _ := c.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(c.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
+}
+
+// Preview
+// @Title 跨品种分析图表-预览数据
+// @Description 跨品种分析图表-获取预览数据
+// // @Param	request	body request.ChartConfigReq true "type json string"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview [post]
+func (c *ChartInfoController) Preview() {
+	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.ChartConfigReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.TagX <= 0 {
+		br.Msg = "请选择X轴坐标的标签"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.TagY <= 0 {
+		br.Msg = "请选择Y轴坐标的标签"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.CalculateValue <= 0 {
+		br.Msg = "请设置时间长度"
+		br.IsSendEmail = false
+		return
+	}
+	if req.CalculateUnit == `` {
+		br.Msg = "请设置时间频度"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 品种配置
+	varietyListList := len(req.VarietyList)
+	if varietyListList < 0 {
+		br.Msg = "请选择品种"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 日期配置
+	dateConfigList := len(req.DateConfigList)
+	if dateConfigList < 0 {
+		br.Msg = "请选择日期"
+		br.IsSendEmail = false
+		return
+	}
+	if dateConfigList > 5 {
+		br.Msg = "日期数量已达上限!"
+		br.IsSendEmail = false
+		return
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+	chartInfo.ChartType = utils.CHART_SOURCE_CROSS_HEDGING
+
+	// 获取图表x轴y轴
+	edbInfoList, dataResp, err, errMsg, isSendEmail := cross_variety.GetChartData(0, req)
+	if err != nil {
+		br.IsSendEmail = isSendEmail
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	resp := response.ChartPreviewResp{
+		EdbInfoList: edbInfoList,
+		DataResp:    dataResp,
+	}
+	//resp.EdbInfoList = edbList
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增图表接口
+// @Description 新增图表接口
+// @Param	request	body request.AddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/add [post]
+func (c *ChartInfoController) Add() {
+	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
+	}
+
+	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
+	var req request.AddChartReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	// 添加图表
+	chartInfo, err, errMsg, isSendEmail := cross_variety.AddChartInfo(req, sysUser)
+	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 = chartInfo.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartInfo.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(c.Ctx.Input.RequestBody)
+		chartLog.Status = "新增跨品种分析图表"
+		chartLog.Method = c.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 request.EditChartInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /chart_info/edit [post]
+func (c *ChartInfoController) Edit() {
+	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.EditChartReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartItem, err, errMsg, isSendEmail := cross_variety.EditChartInfo(req, sysUser)
+	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(c.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑跨品种分析图表"
+		chartLog.Method = c.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"
+// @Success 200 {object} data_manage.ChartInfoDetailResp
+// @router /chart_info/detail [get]
+func (c *ChartInfoController) Detail() {
+	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
+	}
+	chartInfoId, _ := c.GetInt("ChartInfoId")
+	if chartInfoId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	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 chartInfo.ExtraConfig == `` {
+		br.Msg = "图表配置信息异常"
+		br.ErrMsg = "图表配置信息异常"
+		br.IsSendEmail = false
+		return
+	}
+	var config request.ChartConfigReq
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &config)
+	if err != nil {
+		br.Msg = "解析跨品种分析配置失败"
+		br.ErrMsg = "解析跨品种分析配置失败,Err:" + err.Error()
+		return
+	}
+
+	//mappingList, err := cross_varietyModel.GetChartVarietyMappingList(chartInfo.ChartInfoId)
+	//if err != nil {
+	//	br.Msg = "获取品种失败"
+	//	br.ErrMsg = "获取品种失败,Err:" + err.Error()
+	//	return
+	//}
+	// 获取跨品种分析配置
+	//chartInfoCrossVariety, err := cross_varietyModel.GetChartInfoCrossVarietyByChartInfoId(chartInfo.ChartInfoId)
+	//if err != nil {
+	//	br.Msg = "获取跨品种分析配置失败"
+	//	br.ErrMsg = "获取跨品种分析配置失败,Err:" + err.Error()
+	//	return
+	//}
+	//
+	//varietyIdList := make([]int,0)
+	//for _,v:=range mappingList{
+	//	varietyIdList = append(varietyIdList,v.ChartVarietyId)
+	//}
+	//config := request.ChartConfigReq{
+	//	TagX:           chartInfoCrossVariety.ChartXTagId,
+	//	TagY:           chartInfoCrossVariety.ChartYTagId,
+	//	CalculateValue: chartInfoCrossVariety.CalculateValue,
+	//	CalculateUnit:  chartInfoCrossVariety.CalculateUnit,
+	//	DateConfigList: config.DateConfigList,
+	//	VarietyList:    varietyIdList,
+	//}
+	//config.TagX =
+
+	// 获取图表x轴y轴
+	edbList, dataResp, err, errMsg, isSendEmail := cross_variety.GetChartData(0, config)
+	if err != nil {
+		br.IsSendEmail = isSendEmail
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 完善指标信息
+	//edbList, e := correlationServ.GetChartEdbInfoFormat(chartInfo.ChartInfoId, edbInfoMappingA, edbInfoMappingB)
+	//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)
+	////判断是否需要展示英文标识
+	//chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+	//chartInfo.UnitEn = edbInfoMappingA.UnitEn
+
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.DataResp = dataResp
+	resp.EdbInfoList = edbList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Relation
+// @Title 获取图表的关联信息
+// @Description 获取图表的关联信息
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Success 200 {object} data_manage.ChartInfoDetailResp
+// @router /chart_info/relation [get]
+func (c *ChartInfoController) Relation() {
+	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
+	}
+	chartInfoId, _ := c.GetInt("ChartInfoId")
+	if chartInfoId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	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 chartInfo.ExtraConfig == `` {
+		br.Msg = "图表配置信息异常"
+		br.ErrMsg = "图表配置信息异常"
+		br.IsSendEmail = false
+		return
+	}
+	var config request.ChartConfigReq
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &config)
+	if err != nil {
+		br.Msg = "解析跨品种分析配置失败"
+		br.ErrMsg = "解析跨品种分析配置失败,Err:" + err.Error()
+		return
+	}
+
+	tmpTagList, err := cross_varietyModels.GetTagListByIdList([]int{config.TagX, config.TagY})
+	if err != nil {
+		br.Msg = "获取标签信息失败"
+		br.ErrMsg = "获取标签信息失败,Err:" + err.Error()
+		return
+	}
+
+	var xTag, yTag *cross_varietyModels.ChartTag
+	for _, v := range tmpTagList {
+		if v.ChartTagId == config.TagX {
+			xTag = v
+		} else {
+			yTag = v
+		}
+	}
+	tagList := []*cross_varietyModels.ChartTag{
+		xTag, yTag,
+	}
+
+	// 获取品种列表
+	varietyList, err := cross_varietyModels.GetVarietyListByIdList(config.VarietyList)
+	if err != nil {
+		br.Msg = "获取品种信息失败"
+		br.ErrMsg = "获取品种信息失败,Err:" + err.Error()
+		return
+	}
+	resp := response.ChartRelationResp{
+		ChartInfo:   chartInfo,
+		TagList:     tagList,
+		VarietyList: varietyList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Copy
+// @Title 复制并新增图表接口
+// @Description 新增图表接口
+// @Param	request	body data_manage.CopyAddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/copy [post]
+func (c *ChartInfoController) Copy() {
+	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
+	}
+	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(c.Ctx.Input.RequestBody)
+		return
+	}
+	var req request.CopyAddChartInfoReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	// 获取原图表信息
+	oldChartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		br.Msg = "获取原图表信息失败"
+		br.ErrMsg = "获取原图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	var config request.AddChartReq
+	err = json.Unmarshal([]byte(oldChartInfo.ExtraConfig), &config)
+	if err != nil {
+		br.Msg = "原图表信息配置解析异常!"
+		br.ErrMsg = "原图表信息配置解析失败,Err:" + err.Error()
+		return
+	}
+	config.ChartName = req.ChartName
+	config.ChartImage = oldChartInfo.ChartImage
+
+	// 添加图表
+	chartInfo, err, errMsg, isSendEmail := cross_variety.AddChartInfo(config, sysUser)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	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 = chartInfo.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartInfo.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(c.Ctx.Input.RequestBody)
+		chartLog.Status = "复制跨品种分析图表"
+		chartLog.Method = c.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
+}
+
+// Move
+// @Title 移动图表接口
+// @Description 移动图表接口
+// @Success 200 {object} data_manage.MoveChartInfoReq
+// @router /chart_info/move [post]
+func (c *ChartInfoController) Move() {
+	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 data_manage.MoveChartInfoReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "图表id小于等于0"
+		br.IsSendEmail = false
+		return
+	}
+
+	chartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		br.Msg = "移动失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	if chartInfo.Source != utils.CHART_SOURCE_CROSS_HEDGING {
+		br.Msg = "图表异常"
+		br.ErrMsg = "分类异常,不是跨品种分析的图表"
+		return
+	}
+
+	//移动排序
+	updateCol := make([]string, 0)
+	//如果有传入 上一个兄弟节点分类id
+	if req.PrevChartInfoId > 0 {
+		prevChartInfo, err := data_manage.GetChartInfoById(req.PrevChartInfoId)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+			return
+		}
+		if prevChartInfo.ChartClassifyId != chartInfo.ChartClassifyId {
+			br.Msg = "不允许跨分类移动"
+			br.ErrMsg = "不允许跨分类移动"
+			br.IsSendEmail = false
+			return
+		}
+
+		//如果是移动在两个兄弟节点之间
+		if req.NextChartInfoId > 0 {
+			//下一个兄弟节点
+			nextChartInfo, err := data_manage.GetChartInfoById(req.NextChartInfoId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+				return
+			}
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevChartInfo.Sort == nextChartInfo.Sort || prevChartInfo.Sort == chartInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+				_ = data_manage.UpdateChartInfoSortByClassifyId(prevChartInfo.ChartClassifyId, prevChartInfo.Sort, prevChartInfo.ChartInfoId, []int{chartInfo.Source}, updateSortStr)
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextChartInfo.Sort-prevChartInfo.Sort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+					_ = data_manage.UpdateChartInfoSortByClassifyId(prevChartInfo.ChartClassifyId, prevChartInfo.Sort, prevChartInfo.ChartInfoId, []int{chartInfo.Source}, updateSortStr)
+				}
+			}
+		}
+
+		chartInfo.Sort = prevChartInfo.Sort + 1
+		chartInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+
+	} else {
+		firstClassify, err := data_manage.GetFirstChartInfoByClassifyId(chartInfo.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+			return
+		}
+
+		if firstClassify != nil && firstClassify.ChartClassifyId != chartInfo.ChartClassifyId {
+			br.Msg = "不允许跨分类移动"
+			br.ErrMsg = "不允许跨分类移动"
+			br.IsSendEmail = false
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstClassify != nil && firstClassify.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = data_manage.UpdateChartInfoSortByClassifyId(firstClassify.ChartClassifyId, 0, firstClassify.ChartInfoId-1, []int{chartInfo.Source}, updateSortStr)
+		}
+
+		chartInfo.Sort = 0 //那就是排在第一位
+		chartInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = chartInfo.Update(updateCol)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "修改失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	if err != nil {
+		br.Msg = "移动失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(req.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(req.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(c.Ctx.Input.RequestBody)
+		chartLog.Status = "移动跨品种分析图表"
+		chartLog.Method = c.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+// Refresh
+// @Title 图表刷新接口
+// @Description 图表刷新接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   UniqueCode   query   string  true       "唯一code"
+// @Success Ret=200 刷新成功
+// @router /chart_info/refresh [get]
+func (c *ChartInfoController) Refresh() {
+	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
+	}
+	chartInfoId, _ := c.GetInt("ChartInfoId")
+	uniqueCode := c.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
+	}
+
+	var config request.ChartConfigReq
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &config)
+	if err != nil {
+		br.Msg = "解析跨品种分析配置失败"
+		br.ErrMsg = "解析跨品种分析配置失败,Err:" + err.Error()
+		return
+	}
+
+	// 获取关联的指标信息
+	_, _, edbInfoIdList, err := cross_variety.GetXYEdbIdList(config.TagX, config.TagY, config.VarietyList)
+	if err != nil {
+		br.Msg = "刷新失败,获取指标信息失败"
+		br.ErrMsg = "刷新失败,获取指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 批量刷新
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, false, false)
+	if err != nil {
+		return
+	}
+
+	//清除图表缓存
+	{
+		key := utils.HZ_CHART_LIB_DETAIL + chartInfo.UniqueCode
+		_ = utils.Rc.Delete(key)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "刷新成功"
+	if isAsync {
+		br.Msg = "图表关联指标较多,请10分钟后刷新页面查看最新数据"
+	}
+}
+
+// EnInfoEdit
+// @Title 编辑图表英文信息接口
+// @Description 编辑图表英文信息接口
+// @Param	request	body data_manage.EditChartEnInfoReq true "type json string"
+// @Success Ret=200 编辑成功
+// @router /chart_info/en/edit [post]
+func (c *ChartInfoController) EnInfoEdit() {
+	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.EditChartEnInfoReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.ChartNameEn = strings.Trim(req.ChartNameEn, " ")
+	if req.ChartInfoId <= 0 {
+		br.Msg = "请选择图表"
+		return
+	}
+	if req.ChartNameEn == "" {
+		br.Msg = "请输入英文图表名称"
+		return
+	}
+
+	//判断指标名称是否存在
+	chartItem, err := data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,请刷新页面"
+			br.ErrMsg = "图表已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 校验名称是否有重复
+	//{
+	//	var condition string
+	//	var pars []interface{}
+	//	condition += " AND chart_info_id <> ?  AND chart_name_en = ? AND source = ?"
+	//	pars = append(pars, req.ChartInfoId, req.ChartNameEn, utils.CHART_SOURCE_CROSS_HEDGING)
+	//	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.ChartNameEn + "图表名称已存在"
+	//		return
+	//	}
+	//}
+
+	chartItem.ChartNameEn = req.ChartNameEn
+	chartItem.ModifyTime = time.Now().Local()
+	if e := chartItem.Update([]string{"ChartNameEn", "ModifyTime"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新图表信息失败, Err: " + e.Error()
+		return
+	}
+
+	err = cross_varietyModels.EditChartEn(chartItem, req)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "更新图表英文信息失败, Err: " + err.Error()
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	//指标 修改es信息
+	//go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+
+	//新增操作日志
+	{
+		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(c.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑跨品种分析图表英文信息"
+		chartLog.Method = c.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
+}
+
+// DeleteChart
+// @Title 删除跨品种分析分类/图表
+// @Description 删除跨品种分析分类/图表接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /chart_info/delete [post]
+func (c *ChartInfoController) DeleteChart() {
+	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 data_manage.DeleteChartClassifyReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartInfoId <= 0 {
+		br.Msg = "请选择图表"
+		br.IsSendEmail = false
+		return
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	//删除图表
+	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)
+	if !ok {
+		br.Msg = "没有该图表的操作权限"
+		br.ErrMsg = "没有该图表的操作权限"
+		return
+	}
+
+	myIds := make([]int, 0)
+	{
+		// 获取引用该图表的MyCharts, 用于ES删除
+		var myCond string
+		var myPars []interface{}
+		myCond += ` AND a.chart_info_id = ? `
+		myPars = append(myPars, req.ChartInfoId)
+		myCharts, e := data_manage.GetMyChartListGroupByCharyInfoIdAndAdminIdByCondition(myCond, myPars)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取引用图表的MyChats失败, Err: " + e.Error()
+			return
+		}
+		for _, m := range myCharts {
+			myIds = append(myIds, m.MyChartId)
+		}
+	}
+	//删除图表及关联指标
+	err = data_manage.DeleteChartInfoAndData(chartInfo.ChartInfoId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+	//删除ES
+	{
+		go data.EsDeleteChartInfo(chartInfo.ChartInfoId)
+		// 删除MY ETA 图表 es数据
+		go data.EsDeleteMyChartInfoByMyChartIds(myIds)
+	}
+
+	source := chartInfo.Source // 跨品种分析
+	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 tmpCondition string
+		var tmpPars []interface{}
+
+		tmpCondition += " AND level=1 "
+		//pars = append(pars, chartInfo.ChartClassifyId)
+
+		tmpCondition += " AND chart_classify_id>? ORDER BY chart_classify_id ASC LIMIT 1 "
+		tmpPars = append(tmpPars, chartInfo.ChartClassifyId)
+
+		classifyItem, err := data_manage.GetChartClassifyByCondition(tmpCondition, tmpPars)
+		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(c.Ctx.Input.RequestBody)
+		chartLog.Status = "删除图表"
+		chartLog.Method = c.Ctx.Input.URI()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	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 (c *ChartInfoController) SearchByEs() {
+	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
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := c.GetString("Keyword")
+
+	//只看我的
+	isShowMe, _ := c.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	sourceList := []int{utils.CHART_SOURCE_CROSS_HEDGING}
+
+	var searchList []*data_manage.ChartInfo
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		obj := data_manage.EdbInfoNoPermissionAdmin{}
+		confList, err := obj.GetAllChartListByAdminId(c.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
+			//判断是否需要展示英文标识
+			if _, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, []*data_manage.ChartEdbInfoMapping{}, 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
+}
+
+// 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
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	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 {
+					// 这里跟当前用户相关的信息重新查询写入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() {
+						msg = "获取失败"
+						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
+					}
+
+					isOk = true
+					fmt.Println("source redis")
+					return
+				}
+			}
+		}
+	}
+
+	chartInfoId := chartInfo.ChartInfoId
+
+	if chartInfo.ExtraConfig == `` {
+		msg = "图表配置信息异常"
+		errMsg = "图表配置信息异常"
+		return
+	}
+	var config request.ChartConfigReq
+	err := json.Unmarshal([]byte(chartInfo.ExtraConfig), &config)
+	if err != nil {
+		msg = "解析跨品种分析配置失败"
+		errMsg = "解析跨品种分析配置失败,Err:" + err.Error()
+		return
+	}
+	// 获取图表x轴y轴
+	edbList, dataResp, err, msg, _ := cross_variety.GetChartData(0, config)
+	if err != nil {
+		errMsg = "获取图表,指标信息失败,Err:" + err.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)
+	//判断是否需要展示英文标识
+	//chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+	//chartInfo.UnitEn = edbInfoMappingA.UnitEn
+
+	//isSaveAs := true
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	resp.ChartInfo = chartInfo
+	resp.DataResp = dataResp
+	resp.EdbInfoList = edbList
+	resp.Status = true
+
+	// 将数据加入缓存
+	if utils.Re == nil {
+		d, _ := json.Marshal(resp)
+		_ = utils.Rc.Put(key, d, 2*time.Hour)
+	}
+	isOk = true
+	return
+}

+ 554 - 0
controllers/data_manage/cross_variety/classify.go

@@ -0,0 +1,554 @@
+package cross_variety
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// ClassifyController
+// @Description: 跨品种分析分类
+type ClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 跨品种分析分类列表
+// @Description 跨品种分析分类列表接口
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "图表类型,3:跨品种分析,4:滚动跨品种分析"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /classify/list [get]
+func (c *ClassifyController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	resp := new(data_manage.ChartClassifyListResp)
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdMap := make(map[int]bool)
+	{
+		obj := data_manage.EdbInfoNoPermissionAdmin{}
+		confList, err := obj.GetAllChartListByAdminId(c.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, _ := c.GetBool("IsShowMe")
+
+	source, _ := c.GetInt("Source")
+	if source <= 0 {
+		source = utils.CHART_SOURCE_CROSS_HEDGING
+	}
+
+	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_CROSS_HEDGING)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	allChartInfo, err := data_manage.GetChartInfoAll([]int{source})
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	chartInfoMap := make(map[int][]*data_manage.ChartClassifyItems)
+	for _, v := range allChartInfo {
+		if !isShowMe {
+			chartInfoMap[v.ChartClassifyId] = append(chartInfoMap[v.ChartClassifyId], v)
+			continue
+		}
+		if v.SysUserId != c.SysUser.AdminId {
+			continue
+		}
+		chartInfoMap[v.ChartClassifyId] = append(chartInfoMap[v.ChartClassifyId], v)
+	}
+	rootChildMap := make(map[int][]*data_manage.ChartClassifyItems)
+
+	// 移除没有图表的分类
+	allNodes := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range rootList {
+		if v.SysUserId == c.SysUser.AdminId || c.SysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
+			v.Button.OpButton = true
+		}
+
+		rootChildMap[v.ParentId] = append(rootChildMap[v.ParentId], v)
+		if existItems, ok := chartInfoMap[v.ChartClassifyId]; ok {
+			v.Children = existItems
+			allNodes = append(allNodes, v)
+		}
+	}
+
+	// 移除没有权限的图表
+	allNodes = data.HandleNoPermissionChart(allNodes, noPermissionChartIdMap)
+	resp.AllNodes = allNodes
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// EditChartClassify
+// @Title 修改跨品种分析图表分类
+// @Description 修改跨品种分析图表分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /classify/edit [post]
+func (c *ClassifyController) EditChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req data_manage.EditChartClassifyReq
+	err := json.Unmarshal(c.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
+	}
+
+	item, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.Msg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	source := utils.CHART_SOURCE_CROSS_HEDGING
+	if item.ChartClassifyName != req.ChartClassifyName {
+		//count, err := data_manage.GetChartClassifyCount(req.ChartClassifyName, item.ParentId, source)
+		//if err != nil {
+		//	br.Msg = "判断名称是否已存在失败"
+		//	br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		//	return
+		//}
+		//if count > 0 {
+		//	br.Msg = "分类名称已存在,请重新输入"
+		//	br.IsSendEmail = false
+		//	return
+		//}
+
+		err = data_manage.EditChartClassify(req.ChartClassifyId, source, req.ChartClassifyName)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			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 /classify/delete/check [post]
+func (c *ClassifyController) DeleteChartClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req data_manage.ChartClassifyDeleteCheckReq
+	err := json.Unmarshal(c.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 /classify/delete [post]
+func (c *ClassifyController) DeleteChartClassify() {
+	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 data_manage.DeleteChartClassifyReq
+	err := json.Unmarshal(c.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)
+		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
+		}
+		//删除ES
+		{
+			go data.EsDeleteChartInfo(chartInfo.ChartInfoId)
+			// 删除MY ETA 图表 es数据
+			//go data.EsDeleteMyChartInfoByChartInfoId(chartInfo.ChartInfoId)
+			go data.EsDeleteMyChartInfoByMyChartIds(myIds)
+		}
+
+		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(c.Ctx.Input.RequestBody)
+			chartLog.Status = "删除图表"
+			chartLog.Method = c.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 /classify/move [post]
+func (c *ClassifyController) ChartClassifyMove() {
+	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 data_manage.MoveChartClassifyReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		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_CROSS_HEDGING {
+		br.Msg = "分类异常"
+		br.ErrMsg = "分类异常,不是跨品种分析图表的分类"
+		return
+	}
+	updateCol := make([]string, 0)
+
+	//如果有传入 上一个兄弟节点分类id
+	if req.PrevClassifyId > 0 {
+		//上一个兄弟节点
+		prevClassify, err := data_manage.GetChartClassifyById(req.PrevClassifyId)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+			return
+		}
+		if prevClassify.Source != utils.CHART_SOURCE_CROSS_HEDGING {
+			br.Msg = "上级节点分类异常"
+			br.ErrMsg = "上级节点分类异常,不是跨品种分析图表的分类"
+			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.GetFirstChartClassifyByParentIdAndSource(chartClassifyInfo.ParentId, chartClassifyInfo.Source)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取跨品种分析图表分类排序第一条的分类信息失败,Err:" + err.Error()
+			return
+		}
+		if firstClassify != nil && firstClassify.Source != utils.CHART_SOURCE_CROSS_HEDGING {
+			br.Msg = "上级节点分类异常"
+			br.ErrMsg = "上级节点分类异常,不是跨品种分析图表的分类"
+			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 = "移动成功"
+}

+ 408 - 0
controllers/data_manage/cross_variety/tag.go

@@ -0,0 +1,408 @@
+package cross_variety
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage/cross_variety"
+	"eta/eta_api/models/data_manage/cross_variety/request"
+	"eta/eta_api/models/data_manage/cross_variety/response"
+	cross_varietyService "eta/eta_api/services/data/cross_variety"
+	"eta/eta_api/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+// TagController
+// @Description: 标签列表
+type TagController struct {
+	controllers.BaseAuthController
+}
+
+// Add
+// @Title 新增标签
+// @Description 新增标签接口
+// @Param	request	body request.AddTagReq true "type json string"
+// @Success 200 Ret=200 添加成功
+// @router /tag/add [post]
+func (c *TagController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.AddTagReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.TagName == "" {
+		br.Msg = "请输入标签名称"
+		br.IsSendEmail = false
+		return
+	}
+	TagName := utils.TrimStr(req.TagName)
+	item, err := cross_variety.GetTagByName(TagName)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		br.Msg = "添加失败,标签名称不能重复"
+		br.IsSendEmail = false
+		return
+	}
+
+	tag := &cross_variety.ChartTag{
+		ChartTagId:      0,
+		ChartTagName:    TagName,
+		SysUserId:       c.SysUser.AdminId,
+		SysUserRealName: c.SysUser.RealName,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	err = cross_variety.AddTag(tag)
+	if err != nil {
+		br.Msg = "添加标签失败"
+		br.ErrMsg = "添加标签失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "添加成功"
+	br.IsAddLog = true
+	br.Success = true
+}
+
+// Edit
+// @Title 编辑标签接口
+// @Description 编辑标签接口
+// @Param	request	body request.EditTagReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /tag/edit [post]
+func (c *TagController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditTagReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartTagId <= 0 {
+		br.Msg = "请选择标签"
+		br.IsSendEmail = false
+		return
+	}
+	if req.TagName == "" {
+		br.Msg = "请输入标签名称"
+		br.IsSendEmail = false
+		return
+	}
+	TagName := utils.TrimStr(req.TagName)
+
+	item, err := cross_variety.GetTagByName(TagName)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+	if item != nil && item.ChartTagId != req.ChartTagId {
+		br.Msg = "添加失败,标签名称不能重复"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取标签详情
+	varietyInfo, err := cross_variety.GetTagById(req.ChartTagId)
+	if err != nil {
+		br.Msg = "查询标签失败"
+		br.ErrMsg = "查询标签失败;ERR:" + err.Error()
+		return
+	}
+
+	// 编辑
+	varietyInfo.ChartTagName = TagName
+	varietyInfo.ModifyTime = time.Now()
+	err = varietyInfo.Update([]string{"ChartTagName", "ModifyTime"})
+	if err != nil {
+		br.Msg = "修改标签失败"
+		br.ErrMsg = "修改标签失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "修改成功"
+	br.IsAddLog = true
+	br.Success = true
+}
+
+// DeleteCheck
+// @Title 删除标签检测接口
+// @Description 删除标签检测接口
+// @Param	request	body request.DelTagReq true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /tag/delete/check [post]
+func (c *TagController) DeleteCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.DelTagReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartTagId <= 0 {
+		br.Msg = "请选择标签"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+
+	// 获取该标签关联的图表数
+	count, err := cross_variety.GetCountChartByTagId(req.ChartTagId)
+	if err != nil {
+		br.Msg = "检测异常"
+		br.ErrMsg = "检测异常,err:" + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	if count > 0 {
+		deleteStatus = 1
+		tipsMsg = "已关联图表的品种,不允许删除!"
+	}
+
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+
+	resp := response.TagDeleteCheckResp{
+		DeleteStatus: deleteStatus,
+		TipsMsg:      tipsMsg,
+	}
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// Delete
+// @Title 删除标签
+// @Description 删除标签接口
+// @Param	request	body request.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /tag/delete [post]
+func (c *TagController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.DelTagReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartTagId <= 0 {
+		br.Msg = "请选择标签"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取该标签关联的图表数
+	count, err := cross_variety.GetCountChartByTagId(req.ChartTagId)
+	if err != nil {
+		br.Msg = "检测异常"
+		br.ErrMsg = "检测异常,err:" + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	if count > 0 {
+		br.Msg = "已关联图表的品种,不允许删除!"
+		br.ErrMsg = "已关联图表的品种,不允许删除!"
+		br.IsSendEmail = false
+		return
+	}
+
+	varietyInfo, err := cross_variety.GetTagById(req.ChartTagId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "该标签不存在或已删除"
+			br.IsSendEmail = false
+		} else {
+			br.Msg = "删除失败"
+			br.ErrMsg = "查找标签失败,ERR:" + err.Error()
+		}
+		return
+	}
+	err = varietyInfo.Delete()
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除标签失败,ERR:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// List
+// @Title 获取标签中的指标与品种的映射关系
+// @Description 获取标签中的指标与品种的映射关系
+// @Param   ChartTagId   query   int  true       "标签id"
+// @Success 200 Ret=200 保存成功
+// @router /tag/list [get]
+func (c *TagController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	// 获取数据
+	list, err := cross_variety.GetTagItemList()
+	if err != nil {
+		br.Msg = "获取标签失败"
+		br.ErrMsg = "获取标签失败,ERR:" + err.Error()
+		return
+	}
+	dataCount := len(list)
+	page := paging.GetPaging(1, dataCount, dataCount)
+
+	// 获取标签绑定的品种数量
+	tagTotalList, err := cross_variety.GetCountChartTagVarietyItemListByTag()
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取标签绑定的品种数量失败,ERR:" + err.Error()
+		return
+	}
+
+	tagTotalMap := make(map[int]int)
+	for _, v := range tagTotalList {
+		tagTotalMap[v.ChartTagId] = v.Total
+	}
+
+	for k, v := range list {
+		list[k].VarietyTotal = tagTotalMap[v.ChartTagId]
+	}
+
+	resp := response.TagListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Msg = "获取成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// VarietyEdbList
+// @Title 获取标签中的指标与品种的映射关系
+// @Description 获取标签中的指标与品种的映射关系
+// @Param   ChartTagId   query   int  true       "标签id"
+// @Success 200 Ret=200 保存成功
+// @router /tag/variety_edb/list [get]
+func (c *TagController) VarietyEdbList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	chartTagId, _ := c.GetInt("ChartTagId")
+	if chartTagId <= 0 {
+		br.Msg = "请选择标签"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取数据
+	list, err := cross_variety.GetChartTagVarietyItemListByTag(chartTagId)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,ERR:" + err.Error()
+		return
+	}
+	dataCount := len(list)
+	page := paging.GetPaging(1, dataCount, dataCount)
+
+	resp := response.VarietyEdbListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// SaveVarietyEdb
+// @Title 配置标签中的指标与品种的映射关系
+// @Description 配置标签中的指标与品种的映射关系
+// @Param	request	body request.SaveTagVarietyEdbReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /tag/variety_edb/save [post]
+func (c *TagController) SaveVarietyEdb() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.SaveTagVarietyEdbReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartTagId <= 0 {
+		br.Msg = "请选择标签"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 过滤未配置的品种
+	varietyEdbList := make([]request.VarietyEdbReq, 0)
+	for _, v := range req.VarietyEdb {
+		if v.EdbInfoId <= 0 {
+			continue
+		}
+		varietyEdbList = append(varietyEdbList, v)
+	}
+
+	// 保存配置
+	err = cross_variety.SaveVarietyEdb(req.ChartTagId, varietyEdbList, c.SysUser.AdminId, c.SysUser.RealName)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,ERR:" + err.Error()
+		return
+	}
+
+	// 修改跨品种分析图表的关联指标
+	go cross_varietyService.ModifyChartEdbMapping(req.ChartTagId)
+
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.IsAddLog = true
+}

+ 295 - 0
controllers/data_manage/cross_variety/variety.go

@@ -0,0 +1,295 @@
+package cross_variety
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage/cross_variety"
+	"eta/eta_api/models/data_manage/cross_variety/request"
+	"eta/eta_api/models/data_manage/cross_variety/response"
+	"eta/eta_api/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+// VarietyController
+// @Description: 品种列表
+type VarietyController struct {
+	controllers.BaseAuthController
+}
+
+// Add
+// @Title 新增品种
+// @Description 新增品种接口
+// @Param	request	body request.AddVarietyReq true "type json string"
+// @Success 200 Ret=200 添加成功
+// @router /variety/add [post]
+func (c *VarietyController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.AddVarietyReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.VarietyName == "" {
+		br.Msg = "请输入品种名称"
+		br.IsSendEmail = false
+		return
+	}
+	varietyName := utils.TrimStr(req.VarietyName)
+	item, err := cross_variety.GetVarietyByName(varietyName)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		br.Msg = "添加失败,品种名称不能重复"
+		br.IsSendEmail = false
+		return
+	}
+
+	variety := &cross_variety.ChartVariety{
+		ChartVarietyId:   0,
+		ChartVarietyName: varietyName,
+		SysUserId:        c.SysUser.AdminId,
+		SysUserRealName:  c.SysUser.RealName,
+		ModifyTime:       time.Now(),
+		CreateTime:       time.Now(),
+	}
+	err = cross_variety.AddVariety(variety)
+	if err != nil {
+		br.Msg = "添加品种失败"
+		br.ErrMsg = "添加品种失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "添加成功"
+	br.IsAddLog = true
+	br.Success = true
+}
+
+// Edit
+// @Title 编辑品种接口
+// @Description 编辑品种接口
+// @Param	request	body request.EditVarietyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /variety/edit [post]
+func (c *VarietyController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditVarietyReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartVarietyId <= 0 {
+		br.Msg = "请选择品种"
+		br.IsSendEmail = false
+		return
+	}
+	if req.VarietyName == "" {
+		br.Msg = "请输入品种名称"
+		br.IsSendEmail = false
+		return
+	}
+	varietyName := utils.TrimStr(req.VarietyName)
+
+	item, err := cross_variety.GetVarietyByName(varietyName)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+	if item != nil && item.ChartVarietyId != req.ChartVarietyId {
+		br.Msg = "添加失败,品种名称不能重复"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取品种详情
+	varietyInfo, err := cross_variety.GetVarietyById(req.ChartVarietyId)
+	if err != nil {
+		br.Msg = "查询品种失败"
+		br.ErrMsg = "查询品种失败;ERR:" + err.Error()
+		return
+	}
+
+	// 编辑
+	varietyInfo.ChartVarietyName = varietyName
+	varietyInfo.ModifyTime = time.Now()
+	err = varietyInfo.Update([]string{"ChartVarietyName", "ModifyTime"})
+	if err != nil {
+		br.Msg = "修改品种失败"
+		br.ErrMsg = "修改品种失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "修改成功"
+	br.IsAddLog = true
+	br.Success = true
+}
+
+// DeleteCheck
+// @Title 删除品种检测接口
+// @Description 删除品种检测接口
+// @Param	request	body request.DelVarietyReq true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /variety/delete/check [post]
+func (c *VarietyController) DeleteCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.DelVarietyReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartVarietyId <= 0 {
+		br.Msg = "请选择品种"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+
+	// 获取该品种关联的图表数
+	count, err := cross_variety.GetCountChartByVarietyId(req.ChartVarietyId)
+	if err != nil {
+		br.Msg = "检测异常"
+		br.ErrMsg = "检测异常,err:" + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	if count > 0 {
+		deleteStatus = 1
+		tipsMsg = "已关联图表的品种,不允许删除!"
+	}
+
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+	resp := response.VarietyDeleteCheckResp{
+		DeleteStatus: deleteStatus,
+		TipsMsg:      tipsMsg,
+	}
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// Delete
+// @Title 删除品种
+// @Description 删除品种接口
+// @Param	request	body request.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /variety/delete [post]
+func (c *VarietyController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.DelVarietyReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartVarietyId <= 0 {
+		br.Msg = "请选择品种"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取该品种关联的图表数
+	count, err := cross_variety.GetCountChartByVarietyId(req.ChartVarietyId)
+	if err != nil {
+		br.Msg = "检测异常"
+		br.ErrMsg = "检测异常,err:" + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	if count > 0 {
+		br.Msg = "已关联图表的品种,不允许删除!"
+		br.ErrMsg = "已关联图表的品种,不允许删除!"
+		br.IsSendEmail = false
+		return
+	}
+
+	varietyInfo, err := cross_variety.GetVarietyById(req.ChartVarietyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "该品种不存在或已删除"
+			br.IsSendEmail = false
+		} else {
+			br.Msg = "删除失败"
+			br.ErrMsg = "查找品种失败,ERR:" + err.Error()
+		}
+		return
+	}
+	err = varietyInfo.Delete()
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除品种失败,ERR:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// List
+// @Title 获取标签中的指标与品种的映射关系
+// @Description 获取标签中的指标与品种的映射关系
+// @Success 200 Ret=200 保存成功
+// @router /variety/list [get]
+func (c *VarietyController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	// 获取数据
+	list, err := cross_variety.GetVarietyList()
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,ERR:" + err.Error()
+		return
+	}
+	dataCount := len(list)
+	page := paging.GetPaging(1, dataCount, dataCount)
+
+	resp := response.VarietyListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Msg = "获取成功"
+	br.Success = true
+	br.Data = resp
+}

+ 12 - 3
controllers/data_manage/edb_info.go

@@ -1673,7 +1673,13 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				}
 				isAdd = true
 			}
-
+			// 查询基本信息
+			baseIndexInfo, tErr := data_manage.GetBaseIndexInfoByEdbCode(edbCode, source)
+			if tErr == nil {
+				searchItem.EdbName = baseIndexInfo.IndexName
+				searchItem.Frequency = baseIndexInfo.Frequency
+				searchItem.Unit = baseIndexInfo.Unit
+			}
 			// 指标来源于桥接服务: 桥接服务获取指标取频度信息等
 			if sourceItem.BridgeFlag != "" {
 				bridgeOb := data.InitBridgeOB(sourceItem.BridgeFlag)
@@ -2193,6 +2199,9 @@ func (this *EdbInfoController) EdbInfoAdd() {
 		go data_manage.AddEdbInfoLog(edbLog)
 	}
 
+	// 更新es
+	go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+
 	resp := new(data_manage.AddEdbInfoResp)
 	resp.EdbInfoId = edbInfo.EdbInfoId
 	resp.UniqueCode = edbInfo.UniqueCode
@@ -2532,7 +2541,7 @@ func (this *EdbInfoController) EdbInfoRefresh() {
 		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
 		return
 	}
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, false)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, false, false)
 	if err != nil {
 		br.Msg = "刷新失败"
 		br.ErrMsg = "刷新指标失败,EdbInfoRefresh Err:" + err.Error()
@@ -3215,7 +3224,7 @@ func (this *EdbInfoController) EdbInfoAllRefresh() {
 		br.ErrMsg = "数据已被删除,请刷新页面,edbInfoId:" + strconv.Itoa(edbInfoId)
 		return
 	}
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true, false)
 	if err != nil {
 		br.Msg = "刷新失败"
 		br.ErrMsg = "刷新指标失败,EdbInfoRefreshAllFromBase Err:" + err.Error()

+ 18 - 6
controllers/data_manage/edb_info_calculate.go

@@ -134,11 +134,19 @@ func (this *ChartInfoController) CalculateSave() {
 		formulaStr += v.FromTag + ","
 		edbInfoIdBytes = append(edbInfoIdBytes, v.FromTag)
 	}
-	formulaMap := data.CheckFormula(req.CalculateFormula)
-	for _, v := range formulaMap {
-		if !strings.Contains(formulaStr, v) {
-			br.Msg = "公式错误,请重新填写"
-			return
+
+	formulaSlice, err := data.CheckFormulaJson(req.CalculateFormula)
+	if err != nil {
+		br.Msg = "公式格式错误,请重新填写"
+		return
+	}
+	for _, formula := range formulaSlice {
+		formulaMap := data.CheckFormula(formula)
+		for _, v := range formulaMap {
+			if !strings.Contains(formulaStr, v) {
+				br.Msg = "公式错误,请重新填写"
+				return
+			}
 		}
 	}
 
@@ -153,6 +161,8 @@ func (this *ChartInfoController) CalculateSave() {
 		ClassifyId:       req.ClassifyId,
 		CalculateFormula: req.CalculateFormula,
 		EdbInfoIdArr:     req.EdbInfoIdArr,
+		EmptyType:        req.EmptyType,
+		MaxEmptyType:     req.MaxEmptyType,
 	}
 	reqJson, err := json.Marshal(req2)
 	if err != nil {
@@ -422,6 +432,8 @@ func (this *ChartInfoController) CalculateEdit() {
 		ClassifyId:       req.ClassifyId,
 		CalculateFormula: req.CalculateFormula,
 		EdbInfoIdArr:     req.EdbInfoIdArr,
+		EmptyType:        req.EmptyType,
+		MaxEmptyType:     req.MaxEmptyType,
 	}
 	reqJson, err := json.Marshal(req2)
 	if err != nil {
@@ -996,7 +1008,7 @@ func (this *ChartInfoController) CalculateBatchReset() {
 	//}
 
 	// 重新计算
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true, false)
 	if err != nil {
 		fmt.Println(edbInfoId, "RefreshEdbCalculateData err", time.Now())
 		br.Msg = "重新计算失败"

+ 161 - 2
controllers/data_manage/excel/excel_info.go

@@ -1804,6 +1804,9 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 func (c *ExcelInfoController) Refresh() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
 		c.Data["json"] = br
 		c.ServeJSON()
 	}()
@@ -1844,7 +1847,7 @@ func (c *ExcelInfoController) Refresh() {
 	}
 
 	// 数据刷新(只有自定义表格有刷新)
-	if excelDetail.Source == 2 {
+	if excelDetail.Source == utils.TIME_TABLE {
 		jsonStrByte, err := json.Marshal(excelDetail.TableData)
 		if err != nil {
 			br.Msg = "自定义表格数据获取失败"
@@ -1860,7 +1863,7 @@ func (c *ExcelInfoController) Refresh() {
 		}
 
 		if len(tableData.EdbInfoIdList) > 0 {
-			err, _ = data.EdbInfoRefreshAllFromBaseV3(tableData.EdbInfoIdList, false, true)
+			err, _ = data.EdbInfoRefreshAllFromBaseV3(tableData.EdbInfoIdList, false, true, false)
 			if err != nil {
 				br.Msg = "刷新失败"
 				br.ErrMsg = "刷新失败,Err:" + err.Error()
@@ -1869,6 +1872,48 @@ func (c *ExcelInfoController) Refresh() {
 		}
 	}
 
+	// 数据刷新-混合表格
+	if excelDetail.Source == utils.MIXED_TABLE {
+		jsonByte, e := json.Marshal(excelDetail.TableData)
+		if e != nil {
+			br.Msg = "刷新失败"
+			br.ErrMsg = "JSON格式化混合表格数据失败, Err: " + e.Error()
+			return
+		}
+		var tableData request.MixedTableReq
+		if e = json.Unmarshal(jsonByte, &tableData); e != nil {
+			br.Msg = "刷新失败"
+			br.ErrMsg = "解析混合表格数据失败, Err: " + e.Error()
+			return
+		}
+		edbInfoIds := make([]int, 0)
+		edbInfoIdExist := make(map[int]bool)
+		if len(tableData.Data) > 0 {
+			for _, t := range tableData.Data {
+				for _, v := range t {
+					if v.EdbInfoId > 0 && !edbInfoIdExist[v.EdbInfoId] {
+						edbInfoIdExist[v.EdbInfoId] = true
+						edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+					}
+				}
+			}
+		}
+		if len(edbInfoIds) > 0 {
+			e, _ = data.EdbInfoRefreshAllFromBaseV3(edbInfoIds, false, true, false)
+			if e != nil {
+				br.Msg = "刷新失败"
+				br.ErrMsg = "刷新混合表格数据失败, Err: " + err.Error()
+				return
+			}
+		}
+	}
+
+	// 清除缓存
+	key := utils.HZ_CHART_LIB_EXCEL_TABLE_DETAIL + ":" + excelDetail.UniqueCode
+	if utils.Re == nil {
+		_ = utils.Rc.Delete(key)
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "刷新成功"
@@ -2244,3 +2289,117 @@ func (this *ExcelInfoController) MarkEditStatus() {
 	br.Msg = msg
 	br.Data = data
 }
+
+// BatchRefresh
+// @Title 批量刷新表格接口
+// @Description 批量刷新图表接口
+// @Param	request	body excel3.BatchRefreshExcelReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/table/batch_refresh [post]
+func (this *ExcelInfoController) BatchRefresh() {
+	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 excel3.BatchRefreshExcelReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.ExcelCodes) == 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "刷新成功"
+		return
+	}
+
+	// 获取表格关联的指标IDs
+	edbIds, e := excel2.GetEdbIdsFromExcelCodes(req.ExcelCodes)
+	if e != nil {
+		br.Msg = "刷新失败"
+		br.ErrMsg = "获取表格关联的指标IDs失败, Err: " + e.Error()
+		return
+	}
+
+	redisKey := data.GetBatchChartRefreshKey(req.Source, req.ReportId, req.ReportChapterId)
+	refreshKeys := make([]string, 0)
+	for _, v := range req.ExcelCodes {
+		refreshKeys = append(refreshKeys, fmt.Sprint(utils.HZ_CHART_LIB_EXCEL_TABLE_DETAIL, v))
+	}
+
+	// 刷新相关指标
+	syncing, e := data.BatchRefreshEdbByEdbIds(edbIds, redisKey, refreshKeys)
+	if e != nil {
+		br.Msg = "刷新失败"
+		br.ErrMsg = "刷新表格关联指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Msg = "刷新成功"
+	if syncing {
+		br.Msg = "表格关联指标较多,请10分钟后刷新页面查看最新数据"
+	}
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetBatchChartRefreshResult
+// @Title 获取批量刷新表格结果
+// @Description 获取批量刷新表格结果
+// @Param	request	body excel3.BatchRefreshExcelReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/table/batch_refresh/result [post]
+func (this *ExcelInfoController) GetBatchChartRefreshResult() {
+	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 excel3.BatchRefreshExcelReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	// 校验缓存是否存在, 存在说明还在刷新中
+	result := true
+	redisKey := excel2.GetExcelEdbBatchRefreshKey(req.Source, req.ReportId, req.ReportChapterId)
+	if redisKey != `` {
+		// 如果找到了key,那么就是还在更新中
+		ok := utils.Rc.IsExist(redisKey)
+		if ok {
+			result = false
+		}
+	}
+
+	resp := struct {
+		RefreshResult bool `description:"刷新结果"`
+	}{
+		RefreshResult: result,
+	}
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+}

+ 2 - 28
controllers/data_manage/future_good/future_good_chart_classify.go

@@ -56,7 +56,7 @@ func (this *FutureGoodChartClassifyController) ChartClassifyList() {
 			return
 		}
 		// 移除没有权限的图表
-		allNodes := handleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
+		allNodes := data.HandleNoPermissionChart(resp.AllNodes, noPermissionChartIdMap)
 		resp.AllNodes = allNodes
 
 		br.Ret = 200
@@ -97,7 +97,7 @@ func (this *FutureGoodChartClassifyController) ChartClassifyList() {
 	}
 
 	// 移除没有权限的图表
-	allNodes := handleNoPermissionChart(rootList, noPermissionChartIdMap)
+	allNodes := data.HandleNoPermissionChart(rootList, noPermissionChartIdMap)
 	resp.AllNodes = allNodes
 
 	br.Ret = 200
@@ -139,32 +139,6 @@ func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartCl
 	return
 }
 
-// handleNoPermissionChart 图表列表返回,将没有权限的图表移除
-func handleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*data_manage.ChartClassifyItems) {
-	// 移除没有权限的图表
-	newAllNodes = make([]*data_manage.ChartClassifyItems, 0)
-	for _, node := range allNodes {
-		// 二级分类
-		tmpNodeInfo := *node
-		tmpNodeList := make([]*data_manage.ChartClassifyItems, 0)
-
-		if node.Children != nil {
-			for _, chartInfo := range node.Children {
-				// 如果指标不可见,那么就不返回该指标
-				if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
-					continue
-				}
-				tmpNodeList = append(tmpNodeList, chartInfo)
-			}
-		}
-
-		tmpNodeInfo.Children = tmpNodeList
-		newAllNodes = append(newAllNodes, &tmpNodeInfo)
-	}
-
-	return
-}
-
 // ChartClassifyItems
 // @Title 获取所有商品价格图表分类接口-不包含图表
 // @Description 获取所有商品价格图表分类接口-不包含图表

+ 1 - 27
controllers/data_manage/line_equation/line_chart_classify.go

@@ -101,7 +101,7 @@ func (this *LineEquationChartClassifyController) ChartClassifyList() {
 	}
 
 	// 移除没有权限的图表
-	allNodes := handleNoPermissionChart(rootList, noPermissionChartIdMap)
+	allNodes := data.HandleNoPermissionChart(rootList, noPermissionChartIdMap)
 	resp.AllNodes = allNodes
 
 	br.Ret = 200
@@ -144,32 +144,6 @@ func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartCl
 	return
 }
 
-// handleNoPermissionChart 图表列表返回,将没有权限的图表移除
-func handleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*data_manage.ChartClassifyItems) {
-	// 移除没有权限的图表
-	newAllNodes = make([]*data_manage.ChartClassifyItems, 0)
-	for _, node := range allNodes {
-		// 二级分类
-		tmpNodeInfo := *node
-		tmpNodeList := make([]*data_manage.ChartClassifyItems, 0)
-
-		if node.Children != nil {
-			for _, chartInfo := range node.Children {
-				// 如果指标不可见,那么就不返回该指标
-				if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
-					continue
-				}
-				tmpNodeList = append(tmpNodeList, chartInfo)
-			}
-		}
-
-		tmpNodeInfo.Children = tmpNodeList
-		newAllNodes = append(newAllNodes, &tmpNodeInfo)
-	}
-
-	return
-}
-
 // ChartClassifyItems
 // @Title 获取所有拟合方程图表分类接口-不包含图表
 // @Description 获取所有拟合方程图表分类接口-不包含图表

+ 1 - 27
controllers/data_manage/line_feature/classify.go

@@ -101,7 +101,7 @@ func (this *LineFeaturesChartClassifyController) ChartClassifyList() {
 	}
 
 	// 移除没有权限的图表
-	allNodes := handleNoPermissionChart(rootList, noPermissionChartIdMap)
+	allNodes := data.HandleNoPermissionChart(rootList, noPermissionChartIdMap)
 	resp.AllNodes = allNodes
 
 	br.Ret = 200
@@ -144,32 +144,6 @@ func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartCl
 	return
 }
 
-// handleNoPermissionChart 图表列表返回,将没有权限的图表移除
-func handleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*data_manage.ChartClassifyItems) {
-	// 移除没有权限的图表
-	newAllNodes = make([]*data_manage.ChartClassifyItems, 0)
-	for _, node := range allNodes {
-		// 二级分类
-		tmpNodeInfo := *node
-		tmpNodeList := make([]*data_manage.ChartClassifyItems, 0)
-
-		if node.Children != nil {
-			for _, chartInfo := range node.Children {
-				// 如果指标不可见,那么就不返回该指标
-				if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
-					continue
-				}
-				tmpNodeList = append(tmpNodeList, chartInfo)
-			}
-		}
-
-		tmpNodeInfo.Children = tmpNodeList
-		newAllNodes = append(newAllNodes, &tmpNodeInfo)
-	}
-
-	return
-}
-
 // ChartClassifyItems
 // @Title 获取所有统计特征图表分类接口-不包含图表
 // @Description 获取所有统计特征图表分类接口-不包含图表

+ 54 - 14
controllers/data_manage/my_chart.go

@@ -189,6 +189,7 @@ func (this *MyChartController) ChartList() {
 	br.Data = resp
 }
 
+// ClassifyList
 // @Title 我的图表-分类列表接口
 // @Description 我的图表-分类列表接口
 // @Success 200 {object} data_manage.MyChartClassifyResp
@@ -218,14 +219,28 @@ func (this *MyChartController) ClassifyList() {
 
 	resp := new(data_manage.MyChartClassifyResp)
 	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
-		items := make([]*data_manage.MyChartClassify, 0)
+		items := make([]*data_manage.MyChartClassifyItem, 0)
 		resp.List = items
 		br.Ret = 200
 		br.Success = true
 		br.Msg = "获取成功"
 		return
 	}
-	resp.List = list
+	//resp.List = list
+
+	// 获取图表分类下各自的图表数
+	chartsNumMap, e := data.GetMyChartClassifyIdNumMap(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败, GetMyChartClassifyIdNumMap Err: " + e.Error()
+		return
+	}
+
+	results := make([]*data_manage.MyChartClassifyItem, 0)
+	for _, v := range list {
+		results = append(results, data_manage.FormatMyChartClassify2Item(v, chartsNumMap[v.MyChartClassifyId]))
+	}
+	resp.List = results
 
 	language := `CN`
 	// 指标显示的语言
@@ -1670,15 +1685,14 @@ func (this *MyChartController) PublicClassifyList() {
 
 	resp := new(data_manage.PublicChartClassifyResp)
 	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
-		items := make([]data_manage.PublicChartClassifyItem, 0)
-		resp.List = items
+		resp.List = make([]data_manage.PublicChartClassifyList, 0)
 		br.Ret = 200
 		br.Success = true
 		br.Msg = "获取成功"
 		return
 	}
-	publicChartClassifyItemList := make([]data_manage.PublicChartClassifyItem, 0)
 
+	// 创建人姓名
 	adminIdStr := make([]string, 0)
 	for _, v := range list {
 		adminIdStr = append(adminIdStr, fmt.Sprint(v.AdminId))
@@ -1691,22 +1705,42 @@ func (this *MyChartController) PublicClassifyList() {
 		}
 	}
 
+	respList := make([]data_manage.PublicChartClassifyList, 0)
+	existMap := make(map[int]bool, 0)
+	itemsMap := make(map[int][]data_manage.PublicChartClassifyItem, 0)
 	for _, v := range list {
-		realName, ok := adminMap[v.AdminId]
-		if !ok {
-			realName = ``
+		realName := adminMap[v.AdminId]
+		if realName == "" {
+			// 忽略掉被删掉的用户
+			continue
 		}
-		publicChartClassifyItem := data_manage.PublicChartClassifyItem{
+
+		if itemsMap[v.AdminId] == nil {
+			itemsMap[v.AdminId] = make([]data_manage.PublicChartClassifyItem, 0)
+		}
+		itemsMap[v.AdminId] = append(itemsMap[v.AdminId], data_manage.PublicChartClassifyItem{
 			MyChartClassifyId:   v.MyChartClassifyId,
 			MyChartClassifyName: v.MyChartClassifyName,
 			AdminId:             v.AdminId,
 			RealName:            realName,
 			IsPublic:            v.IsPublic,
 			IsCompanyPublic:     v.IsCompanyPublic,
+		})
+
+		var menu data_manage.PublicChartClassifyList
+		if existMap[v.AdminId] {
+			continue
 		}
-		publicChartClassifyItemList = append(publicChartClassifyItemList, publicChartClassifyItem)
+		existMap[v.AdminId] = true
+		menu.MenuAdminId = v.AdminId
+		menu.MenuName = fmt.Sprintf("%s的图库", realName)
+		respList = append(respList, menu)
+	}
+
+	for k, v := range respList {
+		respList[k].Items = itemsMap[v.MenuAdminId]
 	}
-	resp.List = publicChartClassifyItemList
+	resp.List = respList
 
 	language := `CN`
 	// 指标显示的语言
@@ -1995,7 +2029,7 @@ func (this *MyChartController) CompanyPublicClassifyList() {
 
 	resp := new(data_manage.MyChartClassifyResp)
 	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
-		items := make([]*data_manage.MyChartClassify, 0)
+		items := make([]*data_manage.MyChartClassifyItem, 0)
 		resp.List = items
 		br.Ret = 200
 		br.Success = true
@@ -2003,7 +2037,13 @@ func (this *MyChartController) CompanyPublicClassifyList() {
 		return
 	}
 
-	resp.List = list
+	results := make([]*data_manage.MyChartClassifyItem, 0)
+	for _, v := range list {
+		results = append(results, data_manage.FormatMyChartClassify2Item(v, 0))
+	}
+	resp.List = results
+
+	//resp.List = list
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -2066,7 +2106,7 @@ func (this *MyChartController) ClassifyFrameworkNodeList() {
 	}
 	resp := make([]*data_manage.ChartFrameworkNodeItem, 0)
 	for _, v := range nodes {
-		resp = append(resp, data_manage.FormatChartFrameworkNode2Item(v))
+		resp = append(resp, data_manage.FormatChartFrameworkNode2Item(v, 0))
 	}
 
 	br.Data = resp

+ 34 - 38
controllers/data_manage/predict_edb_info.go

@@ -733,7 +733,7 @@ func (this *PredictEdbInfoController) Edit() {
 	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
 
 	// 刷新关联指标
-	go data.EdbInfoRefreshAllFromBaseV2(resp.EdbInfoId, true)
+	go data.EdbInfoRefreshAllFromBaseV2(resp.EdbInfoId, true, false)
 
 	br.Ret = 200
 	br.Success = true
@@ -913,6 +913,8 @@ func (this *PredictEdbInfoController) Detail() {
 				RuleType:         v.RuleType,
 				FixedValue:       v.FixedValue,
 				Value:            v.Value,
+				EmptyType:        v.EmptyType,
+				MaxEmptyType:     v.MaxEmptyType,
 				EndDate:          v.EndDate,
 				ModifyTime:       v.ModifyTime,
 				CreateTime:       v.CreateTime,
@@ -1485,7 +1487,7 @@ func (this *PredictEdbInfoController) ChartDataList() {
 		switch v.RuleType {
 		case 9:
 			// 获取计算参数
-			formula, edbInfoList, edbInfoIdBytes, err, errMsg := data.GetCalculateByRuleByNineParams(v)
+			/*formula, edbInfoList, edbInfoIdBytes, err, errMsg := data.GetCalculateByRuleByNineParams(v)
 			if err != nil {
 				br.Msg = "计算失败"
 				if errMsg != "" {
@@ -1496,13 +1498,26 @@ func (this *PredictEdbInfoController) ChartDataList() {
 				return
 			}
 			// 获取计算数据
-			tmpDataList, err = data.CalculateByRuleByNine(formula, edbInfoList, edbInfoIdBytes)
+			tmpDataList, err = data.CalculateByRuleByNine(formula, edbInfoList, edbInfoIdBytes, v.EmptyType, v.MaxEmptyType)
 			if err != nil {
 				br.Msg = "计算失败"
 				br.ErrMsg = err.Error()
 				br.IsSendEmail = false
 				return
+			}*/
+			reqJson, err := json.Marshal(v)
+			respItem, err := data.PredictCalculateByNinePreview(string(reqJson))
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				return
+			}
+			if respItem.Ret != 200 {
+				br.Msg = respItem.Msg
+				br.ErrMsg = respItem.ErrMsg
+				return
 			}
+			tmpDataList = respItem.Data.DataList
 		}
 		tmpPredictEdbConfAndData := data_manage.PredictEdbConfAndData{
 			ConfigId:         0,
@@ -1641,7 +1656,6 @@ func (this *PredictEdbInfoController) PredictRuleCalculateByNine() {
 		this.ServeJSON()
 	}()
 	sysUser := this.SysUser
-	time.Now().AddDate(0, -1, -1).Format(utils.FormatDate)
 	if sysUser == nil {
 		br.Msg = "请登录"
 		br.ErrMsg = "请登录,SysUser Is Empty"
@@ -1650,49 +1664,31 @@ func (this *PredictEdbInfoController) PredictRuleCalculateByNine() {
 	}
 	var req request.RuleConfig
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-
-	// 获取计算参数
-	formula, edbInfoList, edbInfoIdBytes, err, errMsg := data.GetCalculateByRuleByNineParams(req)
 	if err != nil {
-		br.Msg = "计算失败"
-		if errMsg != "" {
-			br.Msg = errMsg
-		}
-		br.Msg = err.Error()
-		br.IsSendEmail = false
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
-	// 获取计算数据
-	dataList, err := data.CalculateByRuleByNine(formula, edbInfoList, edbInfoIdBytes)
+	// 添加计算指标
+	reqJson, err := json.Marshal(req)
 	if err != nil {
-		br.Msg = "数据计算失败"
-		br.ErrMsg = "数据计算失败:Err:" + err.Error()
-		br.IsSendEmail = false
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
 
-	latestDate := time.Now()
-	for _, v := range edbInfoList {
-		tmpLatestDate, err := time.ParseInLocation(utils.FormatDate, v.LatestDate, time.Local)
-		if err != nil {
-			continue
-		}
-		if tmpLatestDate.Before(latestDate) {
-			latestDate = tmpLatestDate
-		}
-	}
-
-	newDataList := make([]*data_manage.EdbDataList, 0)
-	lenData := len(dataList)
-	if lenData > 0 {
-		for i := lenData - 1; i >= 0; i-- {
-			newDataList = append(newDataList, dataList[i])
-		}
+	respItem, err := data.PredictCalculateByNinePreview(string(reqJson))
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
 	}
-	resp := response.PredictRuleCalculateByNineResp{
-		LatestDate: latestDate.Format(utils.FormatDate),
-		DataList:   newDataList,
+	if respItem.Ret != 200 {
+		br.Msg = respItem.Msg
+		br.ErrMsg = respItem.ErrMsg
+		return
 	}
+	resp := respItem.Data
 
 	br.Ret = 200
 	br.Success = true

+ 443 - 0
controllers/data_manage/yongyi_data.go

@@ -0,0 +1,443 @@
+package data_manage
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+// YongyiClassify
+// @Title 涌益咨询数据分类
+// @Description 涌益咨询数据分类接口
+// @Success 200 {object} data_manage.BaseFromYongyiClassify
+// @router /yongyi/classify [get]
+func (this *EdbInfoController) YongyiClassify() {
+	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
+	}
+
+	classifyAll, err := data_manage.GetAllBaseFromYongyiClassify()
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	var ret data_manage.BaseFromYongyiClassifyResp
+	ret.List = classifyAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// YongyiIndexData
+// @Title 获取钢联数据
+// @Description 获取钢联数据接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   string  true       "分类id"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /yongyi/index/data [get]
+func (this *EdbInfoController) YongyiIndexData() {
+	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 = utils.StartIndex(currentIndex, pageSize)
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+
+	//获取指标
+	var condition string
+	var pars []interface{}
+
+	if classifyId >= 0 {
+		condition += ` AND classify_id=? `
+		pars = append(pars, classifyId)
+	}
+
+	yongyiList, err := data_manage.GetYongyiIndex(condition, pars)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	resultList := make([]*data_manage.BaseFromYongyiIndexList, 0)
+	for _, v := range yongyiList {
+		product := new(data_manage.BaseFromYongyiIndexList)
+		product.YongyiIndexId = v.YongyiIndexId
+		product.Unit = v.Unit
+		product.IndexCode = v.IndexCode
+		product.IndexName = v.IndexName
+		product.Frequency = v.Frequency
+		product.ModifyTime = v.ModifyTime
+
+		total, err := data_manage.GetYongyiIndexDataCount(v.IndexCode)
+		page := paging.GetPaging(currentIndex, pageSize, total)
+		dataList, err := data_manage.GetYongyiIndexData(v.IndexCode, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+		if dataList == nil {
+			dataList = make([]*data_manage.BaseFromYongyiData, 0)
+		}
+		product.DataList = dataList
+		product.Paging = page
+		resultList = append(resultList, product)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resultList
+}
+
+// YongyiSearchList
+// @Title Yongyi模糊搜索
+// @Description Yongyi模糊搜索
+// @Param   Keyword   query   string  ture       "关键字搜索"
+// @Success 200 {object} models.BaseResponse
+// @router /yongyi/search_list [get]
+func (this *EdbInfoController) YongyiSearchList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	list := make([]*data_manage.BaseFromYongyiIndexSearchItem, 0)
+	var err error
+	//关键字
+	keyword := this.GetString("Keyword")
+	if keyword != "" {
+		keyWordArr := strings.Split(keyword, " ")
+
+		if len(keyWordArr) > 0 {
+			condition := ""
+			for _, v := range keyWordArr {
+				condition += ` AND CONCAT(index_name,index_code) LIKE '%` + v + `%'`
+			}
+			list, err = data_manage.GetYongyiItemList(condition)
+			if err != nil {
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				br.Msg = "获取失败"
+				return
+			}
+		}
+
+	} else {
+		// todo es 模糊搜索
+		list, err = data_manage.GetYongyiItemList("")
+		if err != nil {
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			br.Msg = "获取失败"
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// YongyiSingleData
+// @Title 获取Yongyi数据
+// @Description 获取Yongyi单条数据接口
+// @Param   IndexCode   query   string  true       "指标唯一编码"
+// @Success 200 {object} models.BaseResponse
+// @router /yongyi/single_data [get]
+func (this *EdbInfoController) YongyiSingleData() {
+	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
+	}
+	indexCode := this.GetString("IndexCode")
+	indexInfo, err := data_manage.GetBaseFromYongyiIndexByIndexCode(indexCode)
+	if err != nil {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+	dataTmpList, err := data_manage.GetYongyiIndexDataByCode(indexCode)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	var ret data_manage.YongyiSingleDataResp
+	var dataList []*data_manage.YongyiSingleData
+
+	ret.ClassifyId = indexInfo.ClassifyId
+	ret.YongyiIndexId = indexInfo.YongyiIndexId
+	ret.IndexCode = indexInfo.IndexCode
+	ret.IndexName = indexInfo.IndexName
+	ret.Frequency = indexInfo.Frequency
+	ret.CreateTime = indexInfo.CreateTime.Format(utils.FormatDateTime)
+	ret.ModifyTime = indexInfo.ModifyTime.Format(utils.FormatDateTime)
+	ret.Unit = indexInfo.Unit
+	for _, v := range dataTmpList {
+		tmp := &data_manage.YongyiSingleData{
+			Value:    v.Value,
+			DataTime: v.DataTime,
+		}
+		dataList = append(dataList, tmp)
+	}
+	ret.Data = dataList
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// ExportYongyiList
+// @Title 导出Yongyi数据
+// @Description 导出Yongyi数据
+// @Param   IndexName   query   string  false       "名称关键词"
+// @Param   IndexCode   query   string  false       "指标唯一编码"
+// @Param   ClassifyId   query   string  true       "分类"
+// @Success 200  导出成功
+// @router /yongyi/export [get]
+func (this *EdbInfoController) ExportYongyiList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	indexCode := this.GetString("IndexCode") //指标唯一编码
+	classifyId, _ := this.GetInt("ClassifyId")
+	//secNameList := make([]*models.EdbdataExportList, 0)
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+
+	downLoadnFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	var pars []interface{}
+	condition := ""
+	if classifyId > 0 {
+		//获取指标
+		condition += " AND classify_id=?"
+		pars = append(pars, classifyId)
+	}
+	if indexCode != "" {
+		//获取指标
+		condition += " AND index_code=?"
+		pars = append(pars, indexCode)
+	}
+	indexList, err := data_manage.GetYongyiIndex(condition, pars)
+	if err != nil {
+		fmt.Println("获取数据失败,Err:" + err.Error())
+		return
+	}
+	if len(indexList) <= 0 {
+		fmt.Println("indexList 为空")
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "success"
+		return
+	}
+	sheetNew := new(xlsx.Sheet)
+	// todo 分类名称
+	sheetNew, err = xlsxFile.AddSheet("涌益咨询")
+	//sheetNew.SetColWidth()
+	//获取指标数据
+	windRow := sheetNew.AddRow()
+	secNameRow := sheetNew.AddRow()
+	indexCodeRow := sheetNew.AddRow()
+	frequencyRow := sheetNew.AddRow()
+	unitRow := sheetNew.AddRow()
+	lastModifyDateRow := sheetNew.AddRow()
+	//获取分类下指标最大数据量
+	var dataMax int
+	setRowIndex := 6
+	indexCodeList := make([]string, 0)
+	for _, v := range indexList {
+		indexCodeList = append(indexCodeList, v.IndexCode)
+	}
+	dataListMap := make(map[string][]*data_manage.BaseFromYongyiData)
+	if len(indexList) > 0 {
+		allDataList, e := data_manage.GetYongyiIndexDataByCodes(indexCodeList)
+		if e != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + e.Error()
+			return
+		}
+		for _, v := range allDataList {
+			dataListMap[v.IndexCode] = append(dataListMap[v.IndexCode], v)
+		}
+		for _, v := range dataListMap {
+			if len(v) > dataMax {
+				dataMax = len(v)
+			}
+		}
+	}
+
+	for k, sv := range indexList {
+		//获取数据
+		dataList, ok := dataListMap[sv.IndexCode]
+		if !ok {
+			continue
+		}
+		if len(dataList) > 0 {
+			windRow.AddCell().SetValue("涌益咨询")
+			secNameRow.AddCell().SetValue("指标名称")
+			indexCodeRow.AddCell().SetValue("指标ID")
+			frequencyRow.AddCell().SetValue("频率")
+			unitRow.AddCell().SetValue("单位")
+			lastModifyDateRow.AddCell().SetValue("更新时间")
+
+			secNameRow.AddCell().SetValue(sv.IndexName)
+			indexCodeRow.AddCell().SetValue(sv.IndexCode)
+			frequencyRow.AddCell().SetValue(sv.Frequency)
+
+			unitRow.AddCell().SetValue(sv.Unit)
+			lastModifyDateRow.AddCell().SetValue(sv.ModifyTime)
+
+			windRow.AddCell()
+			windRow.AddCell()
+			secNameRow.AddCell()
+			indexCodeRow.AddCell()
+			frequencyRow.AddCell()
+			unitRow.AddCell()
+			lastModifyDateRow.AddCell()
+			min := k * 3
+			sheetNew.SetColWidth(min, min, 15)
+
+			if len(dataList) <= 0 {
+				for n := 0; n < dataMax; n++ {
+					rowIndex := setRowIndex + n
+					row := sheetNew.Row(rowIndex)
+					row.AddCell()
+					row.AddCell()
+					row.AddCell()
+				}
+			} else {
+				endRowIndex := 0
+				for rk, dv := range dataList {
+					rowIndex := setRowIndex + rk
+					row := sheetNew.Row(rowIndex)
+					displayDate, _ := time.Parse(utils.FormatDate, dv.DataTime)
+					displayDateCell := row.AddCell()
+					style := new(xlsx.Style)
+					style.ApplyAlignment = true
+					style.Alignment.WrapText = true
+					displayDateCell.SetStyle(style)
+					displayDateCell.SetDate(displayDate)
+
+					row.AddCell().SetValue(dv.Value)
+					row.AddCell()
+					endRowIndex = rowIndex
+				}
+				if len(dataList) < dataMax {
+					dataLen := dataMax - len(dataList)
+					for n := 0; n < dataLen; n++ {
+						rowIndex := (endRowIndex + 1) + n
+						row := sheetNew.Row(rowIndex)
+						row.AddCell()
+						row.AddCell()
+						row.AddCell()
+					}
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadnFilePath)
+	if err != nil {
+		//有指标无数据时先导出一遍空表
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+		err = xlsxFile.Save(downLoadnFilePath)
+		if err != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+	fileName := `涌益咨询数据`
+	if len(indexList) > 0 {
+		fileName = indexList[0].IndexName
+	}
+	fileName += time.Now().Format("06.01.02") + `.xlsx` //文件名称
+	this.Ctx.Output.Download(downLoadnFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadnFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+
+}

+ 103 - 10
controllers/ppt_english.go

@@ -359,9 +359,19 @@ func (this *PptEnglishController) DeletePpt() {
 func (this *PptEnglishController) DetailPpt() {
 	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
+	}
 	pptId, _ := this.GetInt("PptId")
 
 	pptInfo, err := ppt_english.GetPptEnglishById(pptId)
@@ -371,10 +381,21 @@ func (this *PptEnglishController) DetailPpt() {
 		return
 	}
 
+	// 查询编辑中
+	editor, e := services.UpdatePptEditing(pptId, 0, sysUser.AdminId, sysUser.RealName, true)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新编辑状态失败, err: " + e.Error()
+		return
+	}
+	resp := new(ppt_english.EnglishPPTDetailResp)
+	resp.PptEnglish = pptInfo
+	resp.Editor = editor
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
-	br.Data = pptInfo
+	br.Data = resp
 }
 
 // DownloadPptx
@@ -574,9 +595,19 @@ func (this *PptEnglishController) PptUpload() {
 func (this *PptEnglishController) SaveLog() {
 	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 ppt_english.AddPptEnglishReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -603,17 +634,26 @@ func (this *PptEnglishController) SaveLog() {
 	}
 
 	//变更ppt内容
-	_, err = ppt_english.GetPptEnglishById(int(req.PptId))
-	if err != nil {
-		br.Msg = "信息获取失败"
-		br.ErrMsg = "信息获取失败,Err:" + err.Error()
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "PPT已删除"
-			br.ErrMsg = "PPT已删除"
-			br.IsSendEmail = false
+	pptItem, e := ppt_english.GetPptEnglishById(int(req.PptId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "PPT已被删除, 请刷新页面"
+			return
 		}
+		br.Msg = "信息获取失败"
+		br.ErrMsg = "信息获取失败,Err:" + e.Error()
 		return
 	}
+
+	// 标记编辑状态
+	if pptItem.PptId > 0 {
+		_, e = services.UpdatePptEditing(pptItem.PptId, 1, sysUser.AdminId, sysUser.RealName, true)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新编辑状态失败, err: " + e.Error()
+			return
+		}
+	}
 	//pptInfo.TemplateType = req.FirstPage.TemplateType
 	//pptInfo.BackgroundImg = req.FirstPage.ImgUrl
 	//pptInfo.Title = req.FirstPage.Title
@@ -638,7 +678,9 @@ func (this *PptEnglishController) SaveLog() {
 	}
 	_, err = ppt_english.AddPptEnglishSaveLog(logInfo)
 	if err != nil {
-
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存PPT日志失败, Err: " + err.Error()
+		return
 	}
 	br.Ret = 200
 	br.Success = true
@@ -935,3 +977,54 @@ func (this *PptEnglishController) TitleCheck() {
 	br.Success = true
 	br.Msg = "校验成功"
 }
+
+// Editing
+// @Title 标记编辑状态
+// @Description 标记编辑状态
+// @Param	request	body models.PPTEditingReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /editing [post]
+func (this *PptEnglishController) Editing() {
+	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 models.PPTEditingReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.PptId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	if req.Status <= 0 {
+		br.Msg = "标记状态异常"
+		return
+	}
+
+	editor, e := services.UpdatePptEditing(req.PptId, req.Status, sysUser.AdminId, sysUser.RealName, true)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新编辑状态失败, err: " + e.Error()
+		return
+	}
+
+	br.Data = editor
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 105 - 7
controllers/ppt_v2.go

@@ -360,9 +360,19 @@ func (this *PptV2Controller) DeletePpt() {
 func (this *PptV2Controller) DetailPpt() {
 	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
+	}
 	pptId, _ := this.GetInt("PptId")
 
 	pptInfo, err := models.GetPptV2ById(pptId)
@@ -372,10 +382,21 @@ func (this *PptV2Controller) DetailPpt() {
 		return
 	}
 
+	// 编辑中
+	editor, e := services.UpdatePptEditing(pptId, 0, sysUser.AdminId, sysUser.RealName, false)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新编辑状态失败, err: " + e.Error()
+		return
+	}
+	resp := new(models.PPTDetailResp)
+	resp.PptV2 = pptInfo
+	resp.Editor = editor
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
-	br.Data = pptInfo
+	br.Data = resp
 }
 
 // DownloadPptx
@@ -575,9 +596,19 @@ func (this *PptV2Controller) PptUpload() {
 func (this *PptV2Controller) SaveLog() {
 	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 models.AddPptV2Req
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -604,12 +635,26 @@ func (this *PptV2Controller) SaveLog() {
 	}
 
 	//变更ppt内容
-	_, err = models.GetPptV2ById(int(req.PptId))
-	if err != nil {
+	pptItem, e := models.GetPptV2ById(int(req.PptId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "PPT已被删除, 请刷新页面"
+			return
+		}
 		br.Msg = "信息获取失败"
-		br.ErrMsg = "信息获取失败,Err:" + err.Error()
+		br.ErrMsg = "信息获取失败,Err:" + e.Error()
 		return
 	}
+
+	// 标记编辑状态
+	if pptItem.PptId > 0 {
+		_, e = services.UpdatePptEditing(pptItem.PptId, 1, sysUser.AdminId, sysUser.RealName, false)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新编辑状态失败, err: " + e.Error()
+			return
+		}
+	}
 	//pptInfo.TemplateType = req.FirstPage.TemplateType
 	//pptInfo.BackgroundImg = req.FirstPage.ImgUrl
 	//pptInfo.Title = req.FirstPage.Title
@@ -632,9 +677,11 @@ func (this *PptV2Controller) SaveLog() {
 		AdminRealName: this.SysUser.RealName,
 		CreateTime:    time.Now(),
 	}
-	_, err = models.AddPptV2SaveLog(logInfo)
-	if err != nil {
-
+	_, e = models.AddPptV2SaveLog(logInfo)
+	if e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存PPT日志失败, Err: " + e.Error()
+		return
 	}
 	br.Ret = 200
 	br.Success = true
@@ -1129,3 +1176,54 @@ func (this *PptV2Controller) TitleCheck() {
 	br.Success = true
 	br.Msg = "校验成功"
 }
+
+// Editing
+// @Title 标记编辑状态
+// @Description 标记编辑状态
+// @Param	request	body models.PPTEditingReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /editing [post]
+func (this *PptV2Controller) Editing() {
+	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 models.PPTEditingReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.PptId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	if req.Status <= 0 {
+		br.Msg = "标记状态异常"
+		return
+	}
+
+	editor, e := services.UpdatePptEditing(req.PptId, req.Status, sysUser.AdminId, sysUser.RealName, false)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新编辑状态失败, err: " + e.Error()
+		return
+	}
+
+	br.Data = editor
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 72 - 40
controllers/sandbox/sandbox.go

@@ -998,32 +998,42 @@ func (this *SandboxController) EditSandboxClassify() {
 		return
 	}
 
-	item, err := sandbox.GetSandboxClassifyById(req.SandboxClassifyId)
+	//item, err := sandbox.GetSandboxClassifyById(req.SandboxClassifyId)
+	//if err != nil {
+	//	br.Msg = "保存失败"
+	//	br.Msg = "获取分类信息失败,Err:" + err.Error()
+	//	return
+	//}
+
+	//count, err := sandbox.GetSandboxClassifyCount(req.SandboxClassifyName, item.ParentId)
+	//if err != nil {
+	//	br.Msg = "判断名称是否已存在失败"
+	//	br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+	//	return
+	//}
+	//if count > 0 {
+	//	br.Msg = "分类名称已存在,请重新输入"
+	//	br.IsSendEmail = false
+	//	return
+	//}
+
+	err = sandbox.EditSandboxClassify(req.SandboxClassifyId, req.ChartPermissionId, req.SandboxClassifyName, req.ChartPermissionName)
 	if err != nil {
 		br.Msg = "保存失败"
-		br.Msg = "获取分类信息失败,Err:" + err.Error()
+		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
-
-	if item.SandboxClassifyName != req.SandboxClassifyName {
-		count, err := sandbox.GetSandboxClassifyCount(req.SandboxClassifyName, item.ParentId)
-		if err != nil {
-			br.Msg = "判断名称是否已存在失败"
-			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
-			return
-		}
-		if count > 0 {
-			br.Msg = "分类名称已存在,请重新输入"
-			br.IsSendEmail = false
-			return
-		}
-
-		err = sandbox.EditSandboxClassify(req.SandboxClassifyId, req.SandboxClassifyName)
-		if err != nil {
-			br.Msg = "保存失败"
-			br.ErrMsg = "保存失败,Err:" + err.Error()
-			return
-		}
+	ids, err := sandbox.GetSandboxClassifySubcategories(req.SandboxClassifyId)
+	if err != nil {
+		br.Msg = "查询子级分类id失败"
+		br.ErrMsg = "查询子级分类id失败,Err:" + err.Error()
+		return
+	}
+	err  = sandbox.UpdateSandboxClassifyChartPermissionById(req.ChartPermissionId, req.ChartPermissionName, ids)
+	if err != nil {
+		br.Msg = "修改子级分类错误"
+		br.ErrMsg = "修改子级分类错误,Err:" + err.Error()
+		return
 	}
 	br.Ret = 200
 	br.Msg = "保存成功"
@@ -1246,19 +1256,20 @@ func (this *SandboxController) ChartClassifyMove() {
 		}
 
 		//如果改变了分类,那么移动该图表数据
+		// 11/22 ETA逻辑优化去除名称重复限制
 		if sandboxInfo.SandboxClassifyId != req.ParentClassifyId {
-			//查询需要修改的分类下是否存在同一个图表名称
-			tmpSandboxInfo, tmpErr := sandbox.GetSandboxByClassifyIdAndName(req.ParentClassifyId, sandboxInfo.Name)
-			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
-				br.Msg = "移动失败"
-				br.ErrMsg = "移动失败,Err:" + tmpErr.Error()
-				return
-			}
-			if tmpSandboxInfo != nil {
-				br.Msg = "移动失败,同一个分类下沙盘名称不允许重复"
-				br.ErrMsg = "移动失败,同一个分类下沙盘名称不允许重复"
-				return
-			}
+			////查询需要修改的分类下是否存在同一个图表名称
+			//tmpSandboxInfo, tmpErr := sandbox.GetSandboxByClassifyIdAndName(req.ParentClassifyId, sandboxInfo.Name)
+			//if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			//	br.Msg = "移动失败"
+			//	br.ErrMsg = "移动失败,Err:" + tmpErr.Error()
+			//	return
+			//}
+			//if tmpSandboxInfo != nil {
+			//	br.Msg = "移动失败,同一个分类下沙盘名称不允许重复"
+			//	br.ErrMsg = "移动失败,同一个分类下沙盘名称不允许重复"
+			//	return
+			//}
 			err = sandbox.MoveSandbox(req.SandboxId, req.ParentClassifyId)
 			if err != nil {
 				br.Msg = "移动失败"
@@ -1622,6 +1633,26 @@ func (this *SandboxController) ChartClassifyMove() {
 				br.ErrMsg = "修改失败,Err:" + err.Error()
 				return
 			}
+			if req.ParentClassifyId > 0 {
+				ids, err := sandbox.GetSandboxClassifySubcategories(req.ClassifyId)
+				if err != nil {
+					br.Msg = "查询子级分类id失败"
+					br.ErrMsg = "查询子级分类id失败,Err:" + err.Error()
+					return
+				}
+				parentChartClassifyInfo, err := sandbox.GetSandboxClassifyById(req.ParentClassifyId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+					return
+				}
+				err  = sandbox.UpdateSandboxClassifyChartPermissionById(parentChartClassifyInfo.ChartPermissionId, parentChartClassifyInfo.ChartPermissionName, ids)
+				if err != nil {
+					br.Msg = "修改子级分类错误"
+					br.ErrMsg = "修改子级分类错误,Err:" + err.Error()
+					return
+				}
+			}
 		}
 	}
 
@@ -1792,13 +1823,14 @@ func (this *SandboxController) SaveV2() {
 			PicUrl:            utils.TrimStr(req.PicUrl),
 			ModifyTime:        time.Now(),
 			SandboxClassifyId: req.SandboxClassifyId,
+			Style:             req.Style,
 		}
 		//缩略图为空时不更新
 		var updateSandboxColumn = []string{}
-		if req.PicUrl == ""{
-			updateSandboxColumn = []string{"Content", "MindmapData", "ModifyTime", "SandboxClassifyId"}
+		if req.PicUrl == "" {
+			updateSandboxColumn = []string{"Content", "MindmapData", "ModifyTime", "SandboxClassifyId", "Style"}
 		} else {
-			updateSandboxColumn = []string{"Content", "MindmapData", "PicUrl", "ModifyTime", "SandboxClassifyId"}
+			updateSandboxColumn = []string{"Content", "MindmapData", "PicUrl", "ModifyTime", "SandboxClassifyId", "Style"}
 		}
 		err = sandboxInfo.Update(updateSandboxColumn)
 		if err != nil {
@@ -1890,7 +1922,7 @@ func (this *SandboxController) GetSandboxDetail() {
 	}
 
 	//获取沙盘数据详情(已保存的)
-	sandboxVersionInfo, err := sandbox.GetSandboxById(sandboxId)
+	sandboxInfo, err := sandbox.GetSandboxById(sandboxId)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1900,7 +1932,7 @@ func (this *SandboxController) GetSandboxDetail() {
 	br.Ret = 200
 	br.Success = true
 	br.Msg = msg
-	br.Data = sandboxVersionInfo
+	br.Data = sandboxInfo
 }
 
 //// SandboxClassifyItems
@@ -2372,4 +2404,4 @@ func (this *SandboxController) LinkEdbInfoCheck() {
 	br.Msg = "检测成功"
 	br.Success = true
 	br.Data = resp
-}
+}

+ 14 - 3
controllers/smart_report/smart_report.go

@@ -96,6 +96,11 @@ func (this *SmartReportController) Add() {
 	item.State = smart_report.SmartReportStateWaitPublish
 	item.CreateTime = time.Now().Local()
 	item.ModifyTime = time.Now().Local()
+	item.ContentModifyTime = time.Now().Local()
+	item.HeadImg = req.HeadImg
+	item.EndImg = req.EndImg
+	item.CanvasColor = req.CanvasColor
+	
 	// 继承报告
 	if req.AddType == 2 {
 		ob := new(smart_report.SmartReport)
@@ -231,7 +236,7 @@ func (this *SmartReportController) Edit() {
 		contentModify = true
 	}
 	cols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "Title", "Abstract", "Author",
-		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime"}
+		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime", "HeadImg", "EndImg", "CanvasColor"}
 	item.ClassifyIdFirst = req.ClassifyIdFirst
 	item.ClassifyNameFirst = req.ClassifyNameFirst
 	item.ClassifyIdSecond = req.ClassifyIdSecond
@@ -244,6 +249,9 @@ func (this *SmartReportController) Edit() {
 	item.ContentSub = subContent
 	item.ContentStruct = req.ContentStruct
 	item.ModifyTime = time.Now().Local()
+	item.HeadImg = req.HeadImg
+	item.EndImg = req.EndImg
+	item.CanvasColor = req.CanvasColor
 	if contentModify {
 		//fmt.Println(contentModify)
 		item.LastModifyAdminId = sysUser.AdminId
@@ -754,7 +762,10 @@ func (this *SmartReportController) SaveContent() {
 		item.LastModifyAdminId = sysUser.AdminId
 		item.LastModifyAdminName = sysUser.RealName
 		item.ModifyTime = time.Now().Local()
-		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
+		item.HeadImg = req.HeadImg
+		item.EndImg = req.EndImg
+		item.CanvasColor = req.CanvasColor
+		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "HeadImg", "EndImg", "CanvasColor"}
 		if e = item.Update(cols); e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "更新报告内容失败"
@@ -971,7 +982,7 @@ func (this *SmartReportController) List() {
 		"smart_report_id", "report_code", "classify_id_first", "classify_name_first", "classify_id_second", "classify_name_second", "add_type",
 		"title", "abstract", "author", "frequency", "stage", "video_url", "video_name", "video_play_seconds", "video_size", "detail_img_url", "detail_pdf_url",
 		"admin_id", "admin_real_name", "state", "publish_time", "pre_publish_time", "pre_msg_send", "msg_is_send", "msg_send_time", "create_time", "modify_time",
-		"last_modify_admin_id", "last_modify_admin_name", "content_modify_time", "pv", "uv",
+		"last_modify_admin_id", "last_modify_admin_name", "content_modify_time", "pv", "uv", "head_img", "end_img", "canvas_color",
 	}
 	list, e := reportOB.GetPageItemsByCondition(condition, pars, fields, "", startSize, params.PageSize)
 	if e != nil {

+ 307 - 0
controllers/smart_report/smart_resource.go

@@ -0,0 +1,307 @@
+package smart_report
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/smart_report"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// SmartReportResourceController 智能研报资源库
+type SmartReportResourceController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 资源库列表
+// @Description 资源库列表
+// @Param   PageSize			query	int		true	"每页数据条数"
+// @Param   CurrentIndex		query	int		true	"当前页页码"
+// @Param   Type				query	int		false	"资源类型: 1-版头; 2-版尾"
+// @Param   Keyword				query	string	false	"搜索关键词"
+// @Success 200 {object} smart_report.SmartReportListResp
+// @router /resource/list [get]
+func (this *SmartReportResourceController) List() {
+	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
+	}
+	type SmartReportResourceListReq struct {
+		PageSize     int    `form:"PageSize"`
+		CurrentIndex int    `form:"CurrentIndex"`
+		Type         int    `form:"Type"`
+		Keyword      string `form:"Keyword"`
+	}
+	params := new(SmartReportResourceListReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "入参解析失败, Err: " + e.Error()
+		return
+	}
+
+	var condition string
+	var pars []interface{}
+	// 筛选项
+	{
+		keyword := strings.TrimSpace(params.Keyword)
+		if keyword != "" {
+			kw := fmt.Sprint("%", keyword, "%")
+			condition += fmt.Sprintf(` AND img_name LIKE ?`)
+			pars = append(pars, kw)
+		}
+
+		if params.Type > 0 {
+			condition += ` AND type = ?`
+			pars = append(pars, params.Type)
+		}
+	}
+
+	resp := new(smart_report.SmartReportResourceListResp)
+	reportOB := new(smart_report.SmartReportResource)
+	total, e := reportOB.GetCountByCondition(condition, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取报告总数失败, Err:" + e.Error()
+		return
+	}
+	if total <= 0 {
+		page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+		resp.Paging = page
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	// 分页列表
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+
+	fields := []string{
+		"resource_id",  "create_time", "img_name", "img_url", "type",
+	}
+	list, e := reportOB.GetPageItemsByCondition(condition, pars, fields, startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取资源库分页列表失败, Err:" + e.Error()
+		return
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Rename
+// @Title 重命名
+// @Description 重命名
+// @Param	request	body smart_report.SmartReportPublishReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /resource/rename [post]
+func (this *SmartReportResourceController) Rename() {
+	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 smart_report.SmartReportRenameReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ResourceId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = "报告ID为空"
+		return
+	}
+
+	ob := new(smart_report.SmartReportResource)
+	item, e := ob.GetItemById(req.ResourceId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "资源不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取资源图片失败, Err: " + e.Error()
+		return
+	}
+
+	cols := []string{"ImgName"}
+	item.ImgName = req.ImgName
+
+	if e = item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新资源失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除
+// @Description 删除
+// @Param	request	body smart_report.SmartReportRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /resource/remove [post]
+func (this *SmartReportResourceController) Remove() {
+	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 smart_report.SmartReportResourceRemoveReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ResourceIds == "" {
+		br.Msg = "参数有误"
+		br.ErrMsg = "图片ID为空"
+		return
+	}
+
+	ids := strings.Split(req.ResourceIds, ",")
+	for _, idStr := range ids {
+		ob := new(smart_report.SmartReportResource)
+		id,err := strconv.Atoi(idStr)
+		if err != nil {
+			br.Msg = "参数解析异常!"
+			br.ErrMsg = "参数解析失败,Err:" + err.Error()
+			return
+		}
+		item, e := ob.GetItemById(id)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "操作成功"
+				return
+			}
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取资源失败, Err: " + e.Error()
+			return
+		}
+		if e = item.Del(); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "删除资源失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Add
+// @Title 新增
+// @Description 新增
+// @Param	request	body smart_report.SmartReportAddReq true "type json string"
+// @Success 200 {object} smart_report.SmartReportItem
+// @router /resource/add [post]
+func (this *SmartReportResourceController) Add() {
+	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 smart_report.SmartReportResourceAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.Type != 1 && req.Type != 2 {
+		br.Msg = "请选择新增方式"
+		return
+	}
+
+	req.ImgName = strings.TrimSpace(req.ImgName)
+	if req.ImgName == "" {
+		br.Msg = "请输入图片名称"
+		return
+	}
+
+	item := new(smart_report.SmartReportResource)
+	item.Type = req.Type
+	item.ImgName = req.ImgName
+	item.ImgUrl = req.ImgUrl
+	item.CreateTime = time.Now().Local()
+
+	if e := item.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增资源失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 146 - 0
models/aimod/ai.go

@@ -0,0 +1,146 @@
+package aimod
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type AiChatTopic struct {
+	AiChatTopicId   int `orm:"column(ai_chat_topic_id);pk"`
+	TopicName       string
+	SysUserId       int
+	SysUserRealName string
+	CreateTime      time.Time
+	ModifyTime      time.Time
+}
+
+type AiChat struct {
+	AiChatId        int `orm:"column(ai_chat_id);pk"`
+	AiChatTopicId   int
+	Ask             string
+	AskUuid         string
+	Answer          string
+	Model           string
+	SysUserId       int
+	SysUserRealName string
+	CreateTime      time.Time
+	ModifyTime      time.Time
+}
+
+type ChatReq struct {
+	AiChatTopicId int    `description:"主题id"`
+	Ask           string `description:"提问"`
+}
+
+func GetAiChatByAsk(askUuid string) (item *AiChat, err error) {
+	sql := `SELECT * FROM ai_chat WHERE ask_uuid=?`
+	o := orm.NewOrmUsingDB("ai")
+	err = o.Raw(sql, askUuid).QueryRow(&item)
+	return
+}
+
+type ChatResp struct {
+	AiChatTopicId int    `description:"主题id"`
+	Ask           string `description:"提问"`
+	Answer        string `description:"回答"`
+	Model         string
+}
+
+// AddAiChatTopic 新增主题
+func AddAiChatTopic(item *AiChatTopic) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("ai")
+	lastId, err = o.Insert(item)
+	return
+}
+
+// AddAiChat 新增聊天
+func AddAiChat(item *AiChat) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("ai")
+	lastId, err = o.Insert(item)
+	return
+}
+
+type AiChatTopicView struct {
+	AiChatTopicId int    `description:"主题id"`
+	TopicName     string `description:"主题名称"`
+	CreateTime    string `description:"创建时间"`
+	ModifyTime    string `description:"修改时间"`
+}
+
+func GetAiChatTopicList(sysUserId int) (item []*AiChatTopicView, err error) {
+	sql := ` SELECT * FROM ai_chat_topic WHERE sys_user_id=? ORDER BY create_time DESC `
+	o := orm.NewOrmUsingDB("ai")
+	_, err = o.Raw(sql, sysUserId).QueryRows(&item)
+	return
+}
+
+type AiChatTopicListResp struct {
+	List []*AiChatTopicView
+}
+
+type AiChatView struct {
+	AiChatId      int    `description:"记录id"`
+	AiChatTopicId int    `description:"主题id"`
+	Ask           string `description:"提问"`
+	Answer        string `description:"答案"`
+	Model         string
+	CreateTime    string `description:"创建时间"`
+	ModifyTime    string `description:"修改时间"`
+}
+
+func GetAiChatList(aiChatTopicId int) (item []*AiChatView, err error) {
+	sql := ` SELECT * FROM ai_chat WHERE ai_chat_topic_id=? ORDER BY create_time ASC `
+	o := orm.NewOrmUsingDB("ai")
+	_, err = o.Raw(sql, aiChatTopicId).QueryRows(&item)
+	return
+}
+
+type AiChatDetailResp struct {
+	List []*AiChatView
+}
+
+type TopicDeleteReq struct {
+	AiChatTopicId int `description:"主题id"`
+}
+
+func DeleteTopic(topicId int) (err error) {
+	o := orm.NewOrmUsingDB("ai")
+	tx, err := o.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	sql := ` DELETE FROM ai_chat_topic WHERE  ai_chat_topic_id=? `
+	_, err = tx.Raw(sql, topicId).Exec()
+	if err != nil {
+		return err
+	}
+	sql = ` DELETE FROM ai_chat WHERE  ai_chat_topic_id=? `
+	_, err = tx.Raw(sql, topicId).Exec()
+	if err != nil {
+		return err
+	}
+	return err
+}
+
+type TopicEditReq struct {
+	AiChatTopicId int    `description:"主题id"`
+	TopicName     string `description:"主题名称"`
+}
+
+func GetAiChatTopicByTopicName(topicName string) (item *AiChatTopicView, err error) {
+	sql := ` SELECT * FROM ai_chat_topic WHERE topic_name=? `
+	o := orm.NewOrmUsingDB("ai")
+	err = o.Raw(sql, topicName).QueryRow(&item)
+	return
+}
+
+func EditTopic(topicId int, topicName string) (err error) {
+	o := orm.NewOrmUsingDB("ai")
+	sql := ` UPDATE ai_chat_topic SET topic_name=? WHERE  ai_chat_topic_id=? `
+	_, err = o.Raw(sql, topicName, topicId).Exec()
+	return err
+}

+ 146 - 0
models/data_manage/base_from_yongyi.go

@@ -0,0 +1,146 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type BaseFromYongyiIndex struct {
+	YongyiIndexId int `orm:"column(yongyi_index_id);pk"`
+	ClassifyId    int
+	IndexCode     string
+	IndexName     string
+	Frequency     string
+	Unit          string
+	Sort          int
+	CreateTime    time.Time
+	ModifyTime    time.Time
+}
+
+type BaseFromYongyiIndexList struct {
+	YongyiIndexId int `orm:"column(yongyi_index_id);pk"`
+	ClassifyId    int
+	Interface     string
+	IndexCode     string
+	IndexName     string
+	Frequency     string
+	Unit          string
+	Sort          int
+	CreateTime    string
+	ModifyTime    string
+	DataList      []*BaseFromYongyiData
+	Paging        *paging.PagingItem `description:"分页数据"`
+}
+
+type YongyiSingleDataResp struct {
+	YongyiIndexId int
+	ClassifyId    int
+	IndexCode     string
+	IndexName     string
+	Frequency     string
+	Unit          string
+	StartTime     string
+	CreateTime    string
+	ModifyTime    string
+	Data          []*YongyiSingleData
+}
+
+type YongyiSingleData struct {
+	Value    string `orm:"column(value)" description:"日期"`
+	DataTime string `orm:"column(data_time)" description:"值"`
+}
+
+func GetYongyiIndex(condition string, pars interface{}) (items []*BaseFromYongyiIndexList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_yongyi_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, yongyi_index_id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func GetYongyiIndexDataCount(indexCode string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count  FROM base_from_yongyi_data WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&count)
+	return
+}
+
+func GetYongyiIndexData(indexCode string, startSize, pageSize int) (items []*BaseFromYongyiData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_yongyi_data WHERE index_code=? ORDER BY data_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, indexCode, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetYongyiIndexDataByCodes(indexCode []string) (items []*BaseFromYongyiData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_yongyi_data WHERE index_code in (` + utils.GetOrmInReplace(len(indexCode)) + `) ORDER BY data_time DESC  `
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+type BaseFromYongyiData struct {
+	YongyiDataId  int `orm:"column(yongyi_data_id);pk"`
+	YongyiIndexId int
+	IndexCode     string
+	DataTime      string
+	Value         string
+	CreateTime    string
+	ModifyTime    string
+	DataTimestamp int64
+}
+
+type BaseFromYongyiIndexSearchItem struct {
+	YongyiIndexId int `orm:"column(yongyi_index_id);pk"`
+	ClassifyId    int
+	IndexCode     string
+	IndexName     string
+}
+
+// GetYongyiItemList 模糊查询Yongyi数据库指标列表
+func GetYongyiItemList(condition string) (items []*BaseFromYongyiIndexSearchItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM base_from_yongyi_index  WHERE 1=1"
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetYongyiIndexDataByCode(indexCode string) (list []*BaseFromYongyiData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_yongyi_data WHERE index_code=? `
+	_, err = o.Raw(sql, indexCode).QueryRows(&list)
+	return
+}
+
+func GetBaseFromYongyiIndexByIndexCode(indexCode string) (list *BaseFromYongyiIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_yongyi_index WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&list)
+	return
+}
+
+type BaseFromYongyiIndexType struct {
+	Type2 string `orm:"column(type_2)"`
+	Type3 string `orm:"column(type_3)"`
+}
+
+// Update 更新Yongyi指标基础信息
+func (item *BaseFromYongyiIndex) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+// EditYongyiIndexInfoResp 新增指标的返回
+type EditYongyiIndexInfoResp struct {
+	YongyiIndexId int    `description:"指标ID"`
+	IndexCode     string `description:"指标code"`
+}

+ 223 - 0
models/data_manage/base_from_yongyi_classify.go

@@ -0,0 +1,223 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// BaseFromYongyiClassify Yongyi原始数据分类表
+type BaseFromYongyiClassify struct {
+	ClassifyId      int       `orm:"column(classify_id);pk"`
+	ClassifyName    string    `description:"分类名称"`
+	ParentId        int       `description:"父级id"`
+	SysUserId       int       `description:"创建人id"`
+	SysUserRealName string    `description:"创建人姓名"`
+	Level           int       `description:"层级"`
+	Sort            int       `description:"排序字段,越小越靠前,默认值:10"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+}
+
+// AddBaseFromYongyiClassify 添加Yongyi原始数据分类
+func AddBaseFromYongyiClassify(item *BaseFromYongyiClassify) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	lastId, err = o.Insert(item)
+	return
+}
+
+// GetBaseFromYongyiClassifyCount 获取分类名称的个数
+func GetBaseFromYongyiClassifyCount(classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM base_from_yongyi_classify WHERE classify_name=? AND parent_id=? `
+	err = o.Raw(sql, classifyName, parentId).QueryRow(&count)
+	return
+}
+
+// GetBaseFromYongyiClassifyById 通过分类id的获取分类信息
+func GetBaseFromYongyiClassifyById(classifyId int) (item *BaseFromYongyiClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_yongyi_classify WHERE classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// EditBaseFromYongyiClassify 修改Yongyi原始数据分类
+func EditBaseFromYongyiClassify(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_yongyi_classify SET classify_name=?,modify_time=NOW() WHERE classify_id=? `
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateBaseFromYongyiClassifySort 修改Yongyi原始数据分类的排序
+func UpdateBaseFromYongyiClassifySort(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_yongyi_classify SET sort=classify_id, modify_time=NOW() WHERE classify_id=? `
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}
+
+type BaseFromYongyiClassifyItems struct {
+	ClassifyId      int    `description:"分类ID"`
+	YongyiIndexId   int    `description:"指标类型ID"`
+	YongyiIndexCode string `description:"指标唯一编码"`
+	ClassifyName    string `description:"分类名称"`
+	ParentId        int    `description:"父级id"`
+	Level           int    `description:"层级"`
+	Sort            int    `description:"排序字段,越小越靠前,默认值:10"`
+}
+
+type BaseFromYongyiClassifyNameItems struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级id"`
+}
+
+type BaseFromYongyiClassifyResp struct {
+	List []*BaseFromYongyiClassifyItems
+}
+
+type BaseFromYongyiClassifyNameResp struct {
+	List []*BaseFromYongyiClassifyNameItems
+}
+
+type BaseFromYongyiClassifyItemsButton struct {
+	AddButton    bool `description:"是否可添加"`
+	OpButton     bool `description:"是否可编辑"`
+	DeleteButton bool `description:"是否可删除"`
+	MoveButton   bool `description:"是否可移动"`
+}
+
+// GetBaseFromYongyiClassifyByParentId 根据上级id获取当下的分类列表数据
+func GetBaseFromYongyiClassifyByParentId(parentId int) (items []*BaseFromYongyiClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_yongyi_classify WHERE parent_id=? order by sort asc,classify_id asc`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}
+
+// GetAllBaseFromYongyiClassify 获取所有的分类列表数据
+func GetAllBaseFromYongyiClassify() (items []*BaseFromYongyiClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_yongyi_classify order by sort asc,classify_id asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+type DeleteBaseFromYongyiClassifyReq struct {
+	ClassifyId int `description:"分类id"`
+	EdbInfoId  int `description:"指标id"`
+}
+
+type BaseFromYongyiClassifyListResp struct {
+	AllNodes      []*BaseFromYongyiClassifyItems
+	CanOpClassify bool `description:"是否允许操作分类"`
+}
+
+type BaseFromYongyiClassifySimplify struct {
+	ClassifyId   int    `description:"分类id"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     int
+}
+
+// GetFirstBaseFromYongyiClassify 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstBaseFromYongyiClassify() (item *BaseFromYongyiClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_yongyi_classify order by sort asc,classify_id asc limit 1`
+	err = o.Raw(sql).QueryRow(&item)
+	return
+}
+
+// UpdateBaseFromYongyiClassifySortByClassifyId 根据分类id更新排序
+func UpdateBaseFromYongyiClassifySortByClassifyId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update base_from_yongyi_classify set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// MoveUpYongyiIndexClassifyBySort 往上移动
+func MoveUpYongyiIndexClassifyBySort(parentId, nextSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_yongyi_classify set sort = sort + 1 where parent_id=? and sort >= ? and sort< ?`
+	_, err = o.Raw(sql, parentId, nextSort, currentSort).Exec()
+	return
+}
+
+// MoveDownYongyiIndexClassifyBySort 往下移动
+func MoveDownYongyiIndexClassifyBySort(parentId, prevSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_yongyi_classify set sort = sort - 1 where parent_id=? and sort <= ? and sort> ? `
+	_, err = o.Raw(sql, parentId, prevSort, currentSort).Exec()
+	return
+}
+
+// GetYongyiIndexClassifyMinSort 获取最小不等于0的排序
+func GetYongyiIndexClassifyMinSort(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select min(sort) from base_from_yongyi_classify where parent_id=? and sort <> 0 `
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+// Update 更新分类基础信息
+func (BaseFromYongyiClassify *BaseFromYongyiClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(BaseFromYongyiClassify, cols...)
+	return
+}
+
+type AddYongyiClassifyResp struct {
+	ClassifyId int
+}
+
+// DeleteYongyiClassifyByClassifyId 根据分类id删除对应的指标分类
+func DeleteYongyiClassifyByClassifyId(classifyIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	num := len(classifyIdList)
+	if num <= 0 {
+		return
+	}
+	//删除分类
+	sql := `DELETE FROM base_from_yongyi_classify WHERE classify_id IN (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, classifyIdList).Exec()
+	return
+}
+
+// AddYongyiClassifyMulti 批量新增SMM类别
+func AddYongyiClassifyMulti(list []*BaseFromYongyiClassify) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(1, list)
+	return
+}
+
+// InitYongyiClassifySort 初始化sort值
+func InitYongyiClassifySort() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_yongyi_classify 
+SET modify_time=NOW(), sort = classify_id`
+	_, err = o.Raw(sql).Exec()
+	return
+}
+
+// InitYongyiIndexClassifyId 历史数据的classifyId值
+func InitYongyiIndexClassifyId() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_yongyiindex s
+LEFT JOIN (
+SELECT
+	c1.classify_id,
+	CONCAT( c2.classify_name, c1.classify_name ) AS type_name 
+FROM
+	base_from_yongyi_classify c1
+	LEFT JOIN base_from_yongyi_classify c2 ON c1.parent_id = c2.classify_id 
+	) AS t ON CONCAT( s.type_2, s.type_3 ) = t.type_name
+	SET s.classify_id = t.classify_id, s.modify_time=NOW() where s.type_2 <>""`
+	_, err = o.Raw(sql).Exec()
+	return
+}

+ 23 - 0
models/data_manage/chart_classify.go

@@ -1,6 +1,7 @@
 package data_manage
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
@@ -214,6 +215,14 @@ func GetFirstChartClassifyByParentId(parentId int) (item *ChartClassify, err err
 	return
 }
 
+// GetFirstChartClassifyByParentIdAndSource 获取当前父级图表分类下的排序第一条的数据
+func GetFirstChartClassifyByParentIdAndSource(parentId, source int) (item *ChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE parent_id=?  and source = ? order by sort asc,chart_classify_id asc limit 1`
+	err = o.Raw(sql, parentId, source).QueryRow(&item)
+	return
+}
+
 // UpdateChartClassifySortByParentId 根据图表父类id更新排序
 func UpdateChartClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
 	o := orm.NewOrmUsingDB("data")
@@ -294,3 +303,17 @@ func GetAllChartClassifyItemsBySource(source int) (items []*ChartClassifyItems,
 	_, err = o.Raw(sql, source).QueryRows(&items)
 	return
 }
+
+// GetCrossVarietyChartClassifyBySysUserId
+// @Description: 根据创建人获取跨品种分析的分类
+// @author: Roc
+// @datetime 2023-11-24 14:05:43
+// @param sysUserId int
+// @return item *ChartClassify
+// @return err error
+func GetCrossVarietyChartClassifyBySysUserId(sysUserId int) (item *ChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_classify WHERE source = ? AND sys_user_id=?`
+	err = o.Raw(sql, utils.CHART_SOURCE_CROSS_HEDGING, sysUserId).QueryRow(&item)
+	return
+}

+ 101 - 0
models/data_manage/chart_edb_mapping.go

@@ -2,8 +2,11 @@ package data_manage
 
 import (
 	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
 	"time"
 )
 
@@ -198,3 +201,101 @@ func GetChartEdbMappingByFutureGoodEdbInfoId(edbInfoId int) (item *ChartEdbInfoM
 	err = o.Raw(sql, edbInfoId).QueryRow(&item)
 	return
 }
+
+// ModifyChartEdbMapping
+// @Description: 修改图表的关系表
+// @author: Roc
+// @datetime 2023-12-11 17:23:32
+// @param chartInfoId int
+// @param edbInfoList []*EdbInfo
+// @return err error
+func ModifyChartEdbMapping(chartInfoId int, edbInfoList []*EdbInfo) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("AddCalculateHcz,Err:" + err.Error())
+			_ = o.Rollback()
+		} else {
+			_ = o.Commit()
+		}
+	}()
+	list := make([]*ChartEdbMapping, 0)
+	sql := ` SELECT a.*
+             FROM chart_edb_mapping AS a
+			 WHERE chart_info_id=? 
+             ORDER BY chart_edb_mapping_id ASC `
+	_, err = o.Raw(sql, chartInfoId).QueryRows(&list)
+
+	if err != nil {
+		return
+	}
+
+	mappingIdMap := make(map[int]*ChartEdbMapping)
+	removeMapping := make(map[int]int)
+	for _, v := range list {
+		mappingIdMap[v.EdbInfoId] = v
+		removeMapping[v.EdbInfoId] = v.ChartEdbMappingId
+	}
+
+	addList := make([]*ChartEdbMapping, 0)
+	for _, v := range edbInfoList {
+		_, ok := mappingIdMap[v.EdbInfoId]
+		// 存在该指标关系就不处理了
+		if ok {
+			delete(removeMapping, v.EdbInfoId)
+			continue
+		}
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		// 不存在就添加
+		addList = append(addList, &ChartEdbMapping{
+			//ChartEdbMappingId: 0,
+			ChartInfoId: chartInfoId,
+			EdbInfoId:   v.EdbInfoId,
+			CreateTime:  time.Now(),
+			ModifyTime:  time.Now(),
+			UniqueCode:  utils.MD5(utils.CHART_PREFIX + "_" + fmt.Sprint(chartInfoId) + "_" + fmt.Sprint(v.EdbInfoId) + "_" + timestamp),
+			MaxData:     v.MaxValue,
+			MinData:     v.MinValue,
+			//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,
+		})
+	}
+
+	// 需要添加的话,那就添加吧
+	if len(addList) > 0 {
+		_, err = o.InsertMulti(len(addList), addList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 移除不必要的mapping
+	if len(removeMapping) > 0 {
+		removeIdList := make([]string, 0) //需要移除的日期
+		for _, v := range removeMapping {
+			removeIdList = append(removeIdList, fmt.Sprint(v))
+		}
+		removeIdStr := strings.Join(removeIdList, `","`)
+		removeIdStr = `"` + removeIdStr + `"`
+		//如果拼接指标变更了,那么需要删除所有的指标数据
+		sql := fmt.Sprintf(` DELETE FROM chart_edb_mapping WHERE chart_edb_mapping_id in (%s) `, removeIdStr)
+
+		_, err = o.Raw(sql).Exec()
+		if err != nil {
+			err = fmt.Errorf("移除不必要的mapping失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	return
+}

+ 16 - 13
models/data_manage/chart_framework.go

@@ -258,6 +258,7 @@ type ChartFrameworkAddReq struct {
 // ChartFrameworkNodeReq 图库框架节点请求体
 type ChartFrameworkNodeReq struct {
 	MyChartClassifyId int    `description:"我的图表分类ID"`
+	NodeId            string `description:"节点ID"`
 	NodeName          string `description:"节点名称"`
 }
 
@@ -312,22 +313,23 @@ func GetFirstChartFramework(adminId int) (item *ChartFramework, err error) {
 
 // ChartFrameworkItem 图库框架表信息
 type ChartFrameworkItem struct {
-	ChartFrameworkId int    `description:"框架ID"`
-	FrameworkCode    string `description:"框架唯一编码"`
-	FrameworkName    string `description:"框架名称"`
-	FrameworkImg     string `description:"框架图片"`
-	FrameworkContent string `description:"框架内容"`
-	IsPublic         int    `description:"是否公开:0-私有;1-公开"`
-	PublicTime       string `description:"公开时间"`
-	Sort             int    `description:"排序"`
-	AdminId          int    `description:"创建人ID"`
-	AdminName        string `description:"创建人姓名"`
-	CreateTime       string `description:"创建时间"`
-	ModifyTime       string `description:"更新时间"`
+	ChartFrameworkId int                       `description:"框架ID"`
+	FrameworkCode    string                    `description:"框架唯一编码"`
+	FrameworkName    string                    `description:"框架名称"`
+	FrameworkImg     string                    `description:"框架图片"`
+	FrameworkContent string                    `description:"框架内容"`
+	IsPublic         int                       `description:"是否公开:0-私有;1-公开"`
+	PublicTime       string                    `description:"公开时间"`
+	Sort             int                       `description:"排序"`
+	AdminId          int                       `description:"创建人ID"`
+	AdminName        string                    `description:"创建人姓名"`
+	CreateTime       string                    `description:"创建时间"`
+	ModifyTime       string                    `description:"更新时间"`
+	Nodes            []*ChartFrameworkNodeItem `description:"框架节点"`
 }
 
 // FormatChartFramework2Item 格式化框架信息
-func FormatChartFramework2Item(origin *ChartFramework) (item *ChartFrameworkItem) {
+func FormatChartFramework2Item(origin *ChartFramework, nodes []*ChartFrameworkNodeItem) (item *ChartFrameworkItem) {
 	if origin == nil {
 		return
 	}
@@ -344,6 +346,7 @@ func FormatChartFramework2Item(origin *ChartFramework) (item *ChartFrameworkItem
 	item.AdminName = origin.AdminName
 	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
 	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	item.Nodes = nodes
 	return
 }
 

+ 6 - 1
models/data_manage/chart_framework_node.go

@@ -13,6 +13,7 @@ type ChartFrameworkNode struct {
 	ChartFrameworkNodeId int       `orm:"column(chart_framework_node_id);pk"`
 	ChartFrameworkId     int       `description:"框架ID"`
 	FrameworkName        string    `description:"框架名称"`
+	NodeId               string    `description:"节点ID"`
 	NodeName             string    `description:"节点名称"`
 	MyChartClassifyId    int       `description:"我的图表分类ID"`
 	CreateTime           time.Time `description:"创建时间"`
@@ -114,13 +115,15 @@ type ChartFrameworkNodeItem struct {
 	ChartFrameworkNodeId int
 	ChartFrameworkId     int    `description:"框架ID"`
 	FrameworkName        string `description:"框架名称"`
+	NodeId               string `description:"节点ID"`
 	NodeName             string `description:"节点名称"`
 	MyChartClassifyId    int    `description:"我的图表分类ID"`
+	ChartNum             int    `description:"分类下的图表数"`
 	CreateTime           string `description:"创建时间"`
 }
 
 // FormatChartFrameworkNode2Item 格式化框架节点信息
-func FormatChartFrameworkNode2Item(origin *ChartFrameworkNode) (item *ChartFrameworkNodeItem) {
+func FormatChartFrameworkNode2Item(origin *ChartFrameworkNode, chartNum int) (item *ChartFrameworkNodeItem) {
 	if origin == nil {
 		return
 	}
@@ -128,8 +131,10 @@ func FormatChartFrameworkNode2Item(origin *ChartFrameworkNode) (item *ChartFrame
 	item.ChartFrameworkNodeId = origin.ChartFrameworkNodeId
 	item.ChartFrameworkId = origin.ChartFrameworkId
 	item.FrameworkName = origin.FrameworkName
+	item.NodeId = origin.NodeId
 	item.NodeName = origin.NodeName
 	item.MyChartClassifyId = origin.MyChartClassifyId
+	item.ChartNum = chartNum
 	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
 	return
 }

+ 1 - 1
models/data_manage/chart_info.go

@@ -72,7 +72,7 @@ func GetChartInfoAll(sourceList []int) (items []*ChartClassifyItems, err error)
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT chart_info_id,chart_classify_id,chart_name AS chart_classify_name,
              unique_code,sys_user_id,sys_user_real_name,date_type,start_date,end_date,chart_type,calendar,season_start_date,season_end_date,source
-            FROM chart_info WHERE source in (` + utils.GetOrmInReplace(num) + `)  ORDER BY sort asc,create_time ASC `
+            FROM chart_info WHERE source in (` + utils.GetOrmInReplace(num) + `)  ORDER BY sort asc,chart_info_id ASC `
 	_, err = o.Raw(sql, sourceList).QueryRows(&items)
 	return
 }

+ 264 - 0
models/data_manage/cross_variety/chart_info_cross_variety.go

@@ -0,0 +1,264 @@
+package cross_variety
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/cross_variety/request"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// ChartInfoCrossVariety
+// @Description: 跨品种分析配置表
+type ChartInfoCrossVariety struct {
+	Id             int       `orm:"column(id);pk"`
+	ChartInfoId    int       `description:"图表id"`
+	ChartXTagId    int       `description:"X轴的标签ID"`
+	ChartYTagId    int       `description:"X轴的标签ID"`
+	CalculateValue int       `description:"计算窗口"`
+	CalculateUnit  string    `description:"计算频度"`
+	ModifyTime     time.Time `description:"修改时间"`
+	CreateTime     time.Time `description:"创建时间"`
+}
+
+// GetCountChartByTagId
+// @Description: 根据标签id获取引用该标签的图表数量
+// @author: Roc
+// @datetime 2023-11-27 10:41:46
+// @param tagId int
+// @return total int64
+// @return err error
+func GetCountChartByTagId(tagId int) (total int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT count(1) total FROM chart_info AS a JOIN
+    chart_info_cross_variety AS b on a.chart_info_id = b.chart_info_id 
+    WHERE b.chart_x_tag_id = ? or b.chart_y_tag_id=?`
+	err = o.Raw(sql, tagId, tagId).QueryRow(&total)
+
+	return
+}
+
+// GeChartInfoCrossVarietyListByTagId
+// @Description: 根据标签id获取引用该标签的配置列表
+// @author: Roc
+// @datetime 2023-12-11 13:13:59
+// @param tagId int
+// @return items []*ChartInfoCrossVariety
+// @return err error
+func GeChartInfoCrossVarietyListByTagId(tagId int) (items []*ChartInfoCrossVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT b.* FROM chart_info AS a JOIN
+    chart_info_cross_variety AS b on a.chart_info_id = b.chart_info_id 
+    WHERE b.chart_x_tag_id = ? or b.chart_y_tag_id=?`
+	_, err = o.Raw(sql, tagId, tagId).QueryRows(&items)
+
+	return
+}
+
+// GetChartInfoCrossVarietyByChartInfoId
+// @Description: 根据图表id获取跨品种分析配置信息
+// @author: Roc
+// @datetime 2023-11-24 14:34:50
+// @param id int
+// @return item *ChartInfoCrossVariety
+// @return err error
+func GetChartInfoCrossVarietyByChartInfoId(id int) (item *ChartInfoCrossVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_info_cross_variety WHERE chart_info_id = ?`
+	err = o.Raw(sql, id).QueryRow(&item)
+
+	return
+}
+
+// CreateChart
+// @Description: 新增跨品种图表
+// @author: Roc
+// @datetime 2023-11-24 14:27:31
+// @param chartInfo *data_manage.ChartInfo
+// @param classify *data_manage.ChartClassify
+// @param chartVarietyMappingList []*ChartVarietyMapping
+// @param chartInfoCrossVariety *ChartInfoCrossVariety
+// @return chartInfoId int
+// @return err error
+func CreateChart(chartInfo *data_manage.ChartInfo, classify *data_manage.ChartClassify, chartVarietyMappingList []*ChartVarietyMapping, chartInfoCrossVariety *ChartInfoCrossVariety) (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()
+		}
+	}()
+
+	// 判断是否分类已存在,不存在的话,先添加分类
+	if classify.ChartClassifyId <= 0 {
+		newId, tmpErr := tx.Insert(classify)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		classify.ChartClassifyId = int(newId)
+	}
+
+	// 新增图表信息
+	chartInfo.ChartClassifyId = classify.ChartClassifyId
+	newId, err := tx.Insert(chartInfo)
+	if err != nil {
+		return
+	}
+	// 品种列表
+	chartInfo.ChartInfoId = int(newId)
+	chartInfoId = int(newId)
+
+	if len(chartVarietyMappingList) > 0 {
+		for i := range chartVarietyMappingList {
+			chartVarietyMappingList[i].ChartInfoId = chartInfoId
+		}
+		_, err = tx.InsertMulti(len(chartVarietyMappingList), chartVarietyMappingList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 图表配置
+	chartInfoCrossVariety.ChartInfoId = chartInfoId
+	if _, err = tx.Insert(chartInfoCrossVariety); err != nil {
+		return
+	}
+
+	return
+}
+
+// EditChart 修改相关性图表的 图表与指标 的关系
+func EditChart(chartInfo *data_manage.ChartInfo, chartVarietyMappingList []*ChartVarietyMapping, chartInfoCrossVariety *ChartInfoCrossVariety, chartUpdateCols, chartInfoCrossVarietyUpdateCols []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()
+		}
+	}()
+
+	// 更新图表信息
+	_, err = to.Update(chartInfo, chartUpdateCols...)
+	if err != nil {
+		fmt.Println("UPDATE  chart_info Err:", err.Error())
+		return err
+	}
+
+	// 查找现有的品种列表
+	currVarietyMappingList := make([]*ChartVarietyMapping, 0)
+	sql := `SELECT * FROM chart_variety_mapping WHERE chart_info_id = ? `
+	_, err = to.Raw(sql, chartInfo.ChartInfoId).QueryRows(&currVarietyMappingList)
+	if err != nil {
+		return
+	}
+
+	currVarietyMap := make(map[int]*ChartVarietyMapping)
+	for _, v := range currVarietyMappingList {
+		currVarietyMap[v.ChartVarietyId] = v
+	}
+
+	// 待添加的品种列表
+	addVarietyMappingList := make([]*ChartVarietyMapping, 0)
+
+	for i := range chartVarietyMappingList {
+		_, ok := currVarietyMap[i]
+		if !ok {
+			// 如果不存在,那么就添加
+			chartVarietyMappingList[i].ChartInfoId = chartInfo.ChartInfoId
+			addVarietyMappingList = append(addVarietyMappingList, chartVarietyMappingList[i])
+		} else {
+			// 如果存在那么就移除
+			delete(currVarietyMap, i)
+		}
+	}
+
+	// 添加品种
+	if len(addVarietyMappingList) > 0 {
+		_, err = to.InsertMulti(len(addVarietyMappingList), addVarietyMappingList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除不存在的品种
+	if len(currVarietyMap) > 0 {
+		idStrList := make([]string, 0)
+		for id, _ := range currVarietyMap {
+			idStrList = append(idStrList, fmt.Sprint(id))
+		}
+		removeIdStr := strings.Join(idStrList, `,`)
+		sql = fmt.Sprintf(` DELETE FROM chart_variety_mapping WHERE id in (%s) `, removeIdStr)
+		_, err = to.Raw(sql).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 跨品种分析图表配置
+	_, err = to.Update(chartInfoCrossVariety, chartInfoCrossVarietyUpdateCols...)
+
+	return
+}
+
+// EditChartEn
+// @Description: 修改英文名称
+// @author: Roc
+// @datetime 2023-11-28 21:17:27
+// @param chartInfo *data_manage.ChartInfo
+// @param req request.EditChartEnInfoReq
+// @return err error
+func EditChartEn(chartInfo *data_manage.ChartInfo, req request.EditChartEnInfoReq) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 更新图表信息
+	chartInfo.ChartNameEn = req.ChartNameEn
+	chartInfo.ModifyTime = time.Now().Local()
+	_, err = to.Update(chartInfo, "ChartNameEn", "ModifyTime")
+	if err != nil {
+		fmt.Println("UPDATE  chart_info Err:", err.Error())
+		return err
+	}
+
+	// 更新标签英文名
+	for _, v := range req.TagList {
+		sql := `UPDATE chart_tag SET chart_tag_name_en = ?,modify_time= NOW() WHERE chart_tag_id = ? `
+		_, err = o.Raw(sql, v.TagNameEn, v.ChartTagId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 更新品种英文名
+	for _, v := range req.VarietyList {
+		sql := `UPDATE chart_variety SET chart_variety_name_en = ?,modify_time= NOW() WHERE chart_variety_id = ? `
+		_, err = o.Raw(sql, v.VarietyNameEn, v.ChartVarietyId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 148 - 0
models/data_manage/cross_variety/chart_tag.go

@@ -0,0 +1,148 @@
+package cross_variety
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ChartTag
+// @Description: chart_tag 图表标签表
+type ChartTag struct {
+	ChartTagId      int       `orm:"column(chart_tag_id);pk"`
+	ChartTagName    string    `description:"标签名称"`
+	ChartTagNameEn  string    `description:"标签名称(英文)"`
+	SysUserId       int       `description:"创建人id"`
+	SysUserRealName string    `description:"创建人姓名"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+}
+
+// GetTagById
+// @Description: 根据标签id获取标签详情
+// @author: Roc
+// @datetime 2023-11-21 14:55:31
+// @param id int
+// @return item *ChartTag
+// @return err error
+func GetTagById(id int) (item *ChartTag, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag WHERE chart_tag_id = ?`
+	err = o.Raw(sql, id).QueryRow(&item)
+
+	return
+}
+
+// GetTagByName
+// @Description: 根据标签名称获取标签详情
+// @author: Roc
+// @datetime 2023-11-21 14:55:20
+// @param name string
+// @return item *ChartTag
+// @return err error
+func GetTagByName(name string) (item *ChartTag, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag WHERE chart_tag_name = ?`
+	err = o.Raw(sql, name).QueryRow(&item)
+
+	return
+}
+
+// AddTag
+// @Description: 添加标签
+// @author: Roc
+// @datetime 2023-11-21 14:52:56
+// @param item *ChartTag
+// @return err error
+func AddTag(item *ChartTag) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	lastId, err := o.Insert(item)
+	if err != nil {
+		return
+	}
+
+	item.ChartTagId = int(lastId)
+
+	return
+}
+
+// GetTagList
+// @Description: 获取所有标签列表
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @return items []*ChartTag
+// @return err error
+func GetTagList() (items []*ChartTag, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag WHERE 1 = 1 `
+	_, err = o.Raw(sql).QueryRows(&items)
+
+	return
+}
+
+// Update
+// @Description: 更新标签
+// @author: Roc
+// @receiver item
+// @datetime 2023-11-21 15:15:17
+// @param updateColList []string
+// @return err error
+func (item *ChartTag) Update(updateColList []string) (err error) {
+	to := orm.NewOrmUsingDB("data")
+	_, err = to.Update(item, updateColList...)
+
+	return
+}
+
+// Delete 删除
+func (item *ChartTag) Delete() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Delete(item)
+	return
+}
+
+// GetTagListByIdList
+// @Description: 根据ID列表获取标签列表
+// @author: Roc
+// @datetime 2023-11-23 17:56:39
+// @param idList []int
+// @return items []*ChartTag
+// @return err error
+func GetTagListByIdList(idList []int) (items []*ChartTag, err error) {
+	num := len(idList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag WHERE 1 = 1 AND chart_tag_id in (` + utils.GetOrmInReplace(num) + `)`
+	_, err = o.Raw(sql, idList).QueryRows(&items)
+
+	return
+}
+
+// ChartTagItem
+// @Description: chart_tag 图表标签表
+type ChartTagItem struct {
+	ChartTagId      int       `orm:"column(chart_tag_id);pk"`
+	ChartTagName    string    `description:"标签名称"`
+	ChartTagNameEn  string    `description:"标签名称(英文)"`
+	SysUserId       int       `description:"创建人id"`
+	VarietyTotal    int       `description:"配置关联品种的数量"`
+	SysUserRealName string    `description:"创建人姓名"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+}
+
+// GetTagItemList
+// @Description: 获取所有标签列表(列表页用到的)
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @return items []*ChartTag
+// @return err error
+func GetTagItemList() (items []*ChartTagItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag WHERE 1 = 1 `
+	_, err = o.Raw(sql).QueryRows(&items)
+
+	return
+}

+ 260 - 0
models/data_manage/cross_variety/chart_tag_variety.go

@@ -0,0 +1,260 @@
+package cross_variety
+
+import (
+	"eta/eta_api/models/data_manage/cross_variety/request"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// ChartTagVariety
+// @Description: chart_tag_variety 图表标签品种关系表
+type ChartTagVariety struct {
+	Id                        int       `orm:"column(id);pk"`
+	ChartTagId                int       `description:"标签id"`
+	ChartVarietyId            int       `description:"品种id"`
+	EdbInfoId                 int       `description:"指标id"`
+	LastUpdateSysUserId       int       `description:"最后一次操作人"`
+	LastUpdateSysUserRealName string    `description:"最后一次操作人真实姓名"`
+	ModifyTime                time.Time `description:"修改时间"`
+	CreateTime                time.Time `description:"创建时间"`
+}
+
+// GetChartTagVarietyByTagAndVariety
+// @Description: 根据标签id和品种id获取关系
+// @author: Roc
+// @datetime 2023-11-22 10:42:50
+// @param chartTagId int
+// @param chartVarietyId int
+// @return item *ChartTagVariety
+// @return err error
+func GetChartTagVarietyByTagAndVariety(chartTagId, chartVarietyId int) (item *ChartTagVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag_variety WHERE chart_tag_id = ? AND chart_variety_id = ?`
+	err = o.Raw(sql, chartTagId, chartVarietyId).QueryRow(&item)
+
+	return
+}
+
+// GetChartTagVarietyListByTag
+// @Description: 根据标签id获取所有绑定的品种列表
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @param chartTagId int
+// @param chartVarietyId int
+// @return items []*ChartTagVariety
+// @return err error
+func GetChartTagVarietyListByTag(chartTagId int) (items []*ChartTagVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag_variety WHERE chart_tag_id = ? `
+	_, err = o.Raw(sql, chartTagId).QueryRows(&items)
+
+	return
+}
+
+// GetChartTagVarietyListByTagAndVariety
+// @Description: 根据标签id和品种id列表获取所绑定的品种列表
+// @author: Roc
+// @datetime 2023-11-23 15:04:20
+// @param chartTagId int
+// @param varietyIdList []int
+// @return items []*ChartTagVariety
+// @return err error
+func GetChartTagVarietyListByTagAndVariety(chartTagId int, varietyIdList []int) (items []*ChartTagVariety, err error) {
+	num := len(varietyIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_tag_variety WHERE chart_tag_id = ? AND chart_variety_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, chartTagId, varietyIdList).QueryRows(&items)
+
+	return
+}
+
+// ChartTagVarietyItem
+// @Description: 图表标签/品种/指标数据
+type ChartTagVarietyItem struct {
+	Id             int     `orm:"column(id);pk"`
+	ChartTagId     int     `description:"标签id"`
+	ChartVarietyId int     `description:"品种id"`
+	EdbInfoId      int     `description:"指标id"`
+	EdbCode        string  `description:"指标编码"`
+	EdbName        string  `description:"指标名称"`
+	EndDate        string  `description:"数据的最晚日期"`
+	EndValue       float64 `description:"数据最新值"`
+}
+
+// GetChartTagVarietyItemListByTag
+// @Description: 根据标签id获取所有绑定的品种列表
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @param chartTagId int
+// @param chartVarietyId int
+// @return items []*ChartTagVariety
+// @return err error
+func GetChartTagVarietyItemListByTag(chartTagId int) (items []*ChartTagVarietyItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.*,b.edb_code,b.edb_name,b.end_date,b.end_value FROM chart_tag_variety a 
+         join edb_info b on a.edb_info_id=b.edb_info_id WHERE chart_tag_id = ? `
+	_, err = o.Raw(sql, chartTagId).QueryRows(&items)
+
+	return
+}
+
+// ChartTagVarietyCount
+// @Description: 签绑定的品种数量
+type ChartTagVarietyCount struct {
+	ChartTagId int `description:"标签id"`
+	Total      int `description:"标签配置的品种数量"`
+}
+
+// GetCountChartTagVarietyItemListByTag
+// @Description: 获取所有标签绑定的品种数量
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @return items []*ChartTagVarietyCount
+// @return err error
+func GetCountChartTagVarietyItemListByTag() (items []*ChartTagVarietyCount, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT chart_tag_id,count(1) total FROM chart_tag_variety a 
+         join edb_info b on a.edb_info_id=b.edb_info_id  group by chart_tag_id`
+	_, err = o.Raw(sql).QueryRows(&items)
+
+	return
+}
+
+// SaveVarietyEdb
+// @Description: 配置标签中的指标与品种的映射关系
+// @author: Roc
+// @datetime 2023-11-22 14:26:24
+// @param chartTagId int
+// @param list []request.VarietyEdbReq
+// @param sysUserId int
+// @param sysUserName string
+// @return err error
+func SaveVarietyEdb(chartTagId int, list []request.VarietyEdbReq, sysUserId int, sysUserName string) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+		} else {
+			_ = o.Commit()
+		}
+	}()
+
+	// 查找现在已经存在的品种和指标的关系
+	var items []*ChartTagVariety
+	sql := `SELECT * FROM chart_tag_variety WHERE chart_tag_id = ? `
+	_, err = o.Raw(sql, chartTagId).QueryRows(&items)
+	if err != nil {
+		return
+	}
+
+	hasMap := make(map[int]*ChartTagVariety)
+	existMap := make(map[int]*ChartTagVariety)
+	for _, v := range items {
+		hasMap[v.ChartVarietyId] = v
+		existMap[v.ChartVarietyId] = v
+	}
+
+	// 待添加的配置
+	addList := make([]*ChartTagVariety, 0)
+	for _, v := range list {
+		tmpChartTagVariety, ok := hasMap[v.ChartVarietyId]
+
+		// 找不到就插入
+		if !ok {
+			addList = append(addList, &ChartTagVariety{
+				Id:                        0,
+				ChartTagId:                chartTagId,
+				ChartVarietyId:            v.ChartVarietyId,
+				EdbInfoId:                 v.EdbInfoId,
+				LastUpdateSysUserId:       sysUserId,
+				LastUpdateSysUserRealName: sysUserName,
+				ModifyTime:                time.Now(),
+				CreateTime:                time.Now(),
+			})
+			continue
+		}
+
+		delete(existMap, v.ChartVarietyId)
+
+		// 找到了,如果指标不一致那就修改
+		if tmpChartTagVariety.EdbInfoId != v.EdbInfoId {
+			tmpChartTagVariety.EdbInfoId = v.EdbInfoId
+			tmpChartTagVariety.LastUpdateSysUserId = sysUserId
+			tmpChartTagVariety.LastUpdateSysUserRealName = sysUserName
+			tmpChartTagVariety.ModifyTime = time.Now()
+			_, err = o.Update(tmpChartTagVariety, "EdbInfoId", "LastUpdateSysUserId", "LastUpdateSysUserRealName", "ModifyTime")
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	// 删除不要了的
+	if len(existMap) > 0 {
+		idStrList := make([]string, 0)
+		for _, v := range existMap {
+			idStrList = append(idStrList, fmt.Sprint(v.Id))
+		}
+		removeIdStr := strings.Join(idStrList, `,`)
+		sql = fmt.Sprintf(` DELETE FROM chart_tag_variety WHERE id in (%s) `, removeIdStr)
+		_, err = o.Raw(sql).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 添加配置
+	if len(addList) > 0 {
+		_, err = o.InsertMulti(len(addList), addList)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// GetCountByEdbInfoId
+// @Description: 根据指标id获取其关联的品种数量
+// @author: Roc
+// @datetime 2023-11-28 19:43:42
+// @param edbInfoId int
+// @return total int
+// @return err error
+func GetCountByEdbInfoId(edbInfoId int) (total int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT count(1) total FROM chart_tag_variety a 
+         join edb_info b on a.edb_info_id=b.edb_info_id WHERE a.edb_info_id = ?`
+	err = o.Raw(sql, edbInfoId).QueryRow(&total)
+
+	return
+}
+
+// GetChartTagVarietyListByTagIdList
+// @Description: 根据标签id列表获取所有绑定的品种列表
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @param chartTagId int
+// @param chartVarietyId int
+// @return items []*ChartVariety
+// @return err error
+func GetChartTagVarietyListByTagIdList(chartTagIdList []int) (items []*ChartTagVariety, err error) {
+	num := len(chartTagIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.* FROM chart_tag_variety a 
+         join chart_variety b on a.chart_variety_id=b.chart_variety_id WHERE chart_tag_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, chartTagIdList).QueryRows(&items)
+
+	return
+}

+ 121 - 0
models/data_manage/cross_variety/chart_variety.go

@@ -0,0 +1,121 @@
+package cross_variety
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ChartVariety
+// @Description: chart_variety 图表品种表
+type ChartVariety struct {
+	ChartVarietyId     int       `orm:"column(chart_variety_id);pk"`
+	ChartVarietyName   string    `description:"品种名称"`
+	ChartVarietyNameEn string    `description:"品种名称(英文)"`
+	SysUserId          int       `description:"创建人id"`
+	SysUserRealName    string    `description:"创建人姓名"`
+	ModifyTime         time.Time `description:"修改时间"`
+	CreateTime         time.Time `description:"创建时间"`
+}
+
+// GetVarietyById
+// @Description: 根据品种id获取品种详情
+// @author: Roc
+// @datetime 2023-11-21 14:55:31
+// @param id int
+// @return item *ChartVariety
+// @return err error
+func GetVarietyById(id int) (item *ChartVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_variety WHERE chart_variety_id = ?`
+	err = o.Raw(sql, id).QueryRow(&item)
+
+	return
+}
+
+// GetVarietyByName
+// @Description: 根据品种名称获取品种详情
+// @author: Roc
+// @datetime 2023-11-21 14:55:20
+// @param name string
+// @return item *ChartVariety
+// @return err error
+func GetVarietyByName(name string) (item *ChartVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_variety WHERE chart_variety_name = ?`
+	err = o.Raw(sql, name).QueryRow(&item)
+
+	return
+}
+
+// AddVariety
+// @Description: 添加品种
+// @author: Roc
+// @datetime 2023-11-21 14:52:56
+// @param item *ChartVariety
+// @return err error
+func AddVariety(item *ChartVariety) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	lastId, err := o.Insert(item)
+	if err != nil {
+		return
+	}
+
+	item.ChartVarietyId = int(lastId)
+
+	return
+}
+
+// Update
+// @Description: 更新品种
+// @author: Roc
+// @receiver item
+// @datetime 2023-11-21 15:15:17
+// @param updateColList []string
+// @return err error
+func (item *ChartVariety) Update(updateColList []string) (err error) {
+	to := orm.NewOrmUsingDB("data")
+	_, err = to.Update(item, updateColList...)
+
+	return
+}
+
+// Delete 删除
+func (item *ChartVariety) Delete() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Delete(item)
+	return
+}
+
+// GetVarietyList
+// @Description: 获取所有品种列表
+// @author: Roc
+// @datetime 2023-11-22 10:44:35
+// @return items []*ChartVariety
+// @return err error
+func GetVarietyList() (items []*ChartVariety, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_variety WHERE 1 = 1 `
+	_, err = o.Raw(sql).QueryRows(&items)
+
+	return
+}
+
+// GetVarietyListByIdList
+// @Description: 根据ID列表获取品种列表
+// @author: Roc
+// @datetime 2023-11-23 17:56:39
+// @param idList []int
+// @return items []*ChartVariety
+// @return err error
+func GetVarietyListByIdList(idList []int) (items []*ChartVariety, err error) {
+	num := len(idList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_variety WHERE 1 = 1 AND chart_variety_id in (` + utils.GetOrmInReplace(num) + `)`
+	_, err = o.Raw(sql, idList).QueryRows(&items)
+
+	return
+}

+ 67 - 0
models/data_manage/cross_variety/chart_variety_mapping.go

@@ -0,0 +1,67 @@
+package cross_variety
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ChartVarietyMapping
+// @Description: 图表与品种的关系表
+type ChartVarietyMapping struct {
+	Id             int       `orm:"column(id);pk"`
+	ChartInfoId    int       `description:"图表id"`
+	ChartVarietyId int       `description:"品种id"`
+	ModifyTime     time.Time `description:"修改时间"`
+	CreateTime     time.Time `description:"创建时间"`
+}
+
+// GetChartVarietyMappingList
+// @Description: 获取图表与品种的关系列表表
+// @author: Roc
+// @datetime 2023-11-24 15:22:36
+// @param chartInfoId int
+// @return items []*ChartVarietyMapping
+// @return err error
+func GetChartVarietyMappingList(chartInfoId int) (items []*ChartVarietyMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_variety_mapping WHERE chart_info_id = ? `
+	_, err = o.Raw(sql, chartInfoId).QueryRows(&items)
+	return
+}
+
+// GetCountChartByVarietyId
+// @Description: 根据品种id获取引用该标签的图表数量
+// @author: Roc
+// @datetime 2023-11-27 10:41:46
+// @param tagId int
+// @return total int64
+// @return err error
+func GetCountChartByVarietyId(varietyId int) (total int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT count(1) total FROM chart_info AS a JOIN
+    chart_variety_mapping AS b on a.chart_info_id = b.chart_info_id 
+    WHERE b.chart_variety_id = ? `
+	err = o.Raw(sql, varietyId).QueryRow(&total)
+
+	return
+}
+
+// GetChartVarietyMappingListByChartInfoIdList
+// @Description: 根据图表id列表获取图表与品种的关系列表
+// @author: Roc
+// @datetime 2023-11-24 15:22:36
+// @param chartInfoId int
+// @return items []*ChartVarietyMapping
+// @return err error
+func GetChartVarietyMappingListByChartInfoIdList(chartInfoIdList []int) (items []*ChartVarietyMapping, err error) {
+	num := len(chartInfoIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_variety_mapping WHERE chart_info_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, chartInfoIdList).QueryRows(&items)
+
+	return
+}

+ 80 - 0
models/data_manage/cross_variety/request/chart.go

@@ -0,0 +1,80 @@
+package request
+
+// ChartConfigReq
+// @Description: 跨品种分析的图表配置
+type ChartConfigReq struct {
+	TagX           int               `description:"X轴的标签ID"`
+	TagY           int               `description:"Y轴的标签ID"`
+	CalculateValue int               `description:"计算窗口"`
+	CalculateUnit  string            `description:"计算频度"`
+	DateConfigList []ChartConfigDate `description:"日期配置列表"`
+	VarietyList    []int             `description:"品种id列表"`
+}
+
+// ChartConfigDate
+// @Description: 跨品种分析的日期配置
+type ChartConfigDate struct {
+	DateType int `description:"日期类型,,1:最新日期;2:N天前"`
+	Num      int
+}
+
+// AddChartReq
+// @Description: 添加图表的请求
+type AddChartReq struct {
+	ChartName      string            `description:"图表名称"`
+	LeftMin        string            `description:"图表左侧最小值"`
+	LeftMax        string            `description:"图表左侧最大值"`
+	ChartImage     string            `description:"图表截图,复制的时候才用到" json:"-"`
+	TagX           int               `description:"X轴的标签ID"`
+	TagY           int               `description:"Y轴的标签ID"`
+	CalculateValue int               `description:"计算窗口"`
+	CalculateUnit  string            `description:"计算频度"`
+	DateConfigList []ChartConfigDate `description:"日期配置列表"`
+	VarietyList    []int
+}
+
+// EditChartReq
+// @Description: 编辑图表的请求
+type EditChartReq struct {
+	ChartInfoId    int               `description:"图表id"`
+	ChartName      string            `description:"图表名称"`
+	LeftMin        string            `description:"图表左侧最小值"`
+	LeftMax        string            `description:"图表左侧最大值"`
+	ChartImage     string            `description:"图表截图,复制的时候才用到" json:"-"`
+	TagX           int               `description:"X轴的标签ID"`
+	TagY           int               `description:"Y轴的标签ID"`
+	CalculateValue int               `description:"计算窗口"`
+	CalculateUnit  string            `description:"计算频度"`
+	DateConfigList []ChartConfigDate `description:"日期配置列表"`
+	VarietyList    []int
+}
+
+// CopyAddChartInfoReq
+// @Description: 复制并新增图表
+type CopyAddChartInfoReq struct {
+	ChartInfoId int    `description:"待复制的图表id"`
+	ChartName   string `description:"图表名称"`
+}
+
+// EditChartEnInfoReq
+// @Description: 编辑图表英文信息
+type EditChartEnInfoReq struct {
+	ChartInfoId int                `description:"图表ID"`
+	ChartNameEn string             `description:"英文图表名称"`
+	TagList     []TagNameEnReq     `description:"标签名称"`
+	VarietyList []VarietyNameEnReq `description:"标签名称"`
+}
+
+// TagNameEnReq
+// @Description: 标签英文名称修改
+type TagNameEnReq struct {
+	ChartTagId int    `json:"ChartTagId"`
+	TagNameEn  string `json:"TagNameEn"`
+}
+
+// VarietyNameEnReq
+// @Description: 品种英文名称修改
+type VarietyNameEnReq struct {
+	ChartVarietyId int    `json:"ChartVarietyId"`
+	VarietyNameEn  string `json:"VarietyNameEn"`
+}

+ 31 - 0
models/data_manage/cross_variety/request/tag.go

@@ -0,0 +1,31 @@
+package request
+
+// AddTagReq 添加标签请求
+type AddTagReq struct {
+	TagName string `description:"标签名称"`
+}
+
+// EditTagReq 编辑标签请求
+type EditTagReq struct {
+	ChartTagId int    `description:"标签id"`
+	TagName    string `description:"标签名称"`
+}
+
+// DelTagReq 删除标签请求
+type DelTagReq struct {
+	ChartTagId int `description:"标签id"`
+}
+
+// SaveTagVarietyEdbReq
+// @Description: 配置标签中的指标与品种的映射关系
+type SaveTagVarietyEdbReq struct {
+	ChartTagId int             `description:"标签id"`
+	VarietyEdb []VarietyEdbReq `description:"品种和指标的映射关系"`
+}
+
+// VarietyEdbReq
+// @Description: 品种和指标的映射关系
+type VarietyEdbReq struct {
+	ChartVarietyId int `description:"品种id"`
+	EdbInfoId      int `description:"指标id"`
+}

+ 17 - 0
models/data_manage/cross_variety/request/variety.go

@@ -0,0 +1,17 @@
+package request
+
+// AddVarietyReq 添加品种请求
+type AddVarietyReq struct {
+	VarietyName string `description:"品种名称"`
+}
+
+// EditVarietyReq 编辑品种请求
+type EditVarietyReq struct {
+	ChartVarietyId int    `description:"品种id"`
+	VarietyName    string `description:"品种名称"`
+}
+
+// DelVarietyReq 删除品种请求
+type DelVarietyReq struct {
+	ChartVarietyId int `description:"品种id"`
+}

+ 21 - 0
models/data_manage/cross_variety/response/chart.go

@@ -0,0 +1,21 @@
+package response
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/cross_variety"
+)
+
+// ChartPreviewResp
+// @Description: 图表预览返回
+type ChartPreviewResp struct {
+	EdbInfoList []*data_manage.ChartEdbInfoMapping
+	DataResp    interface{} `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
+}
+
+// ChartRelationResp
+// @Description: 图表关联配置返回
+type ChartRelationResp struct {
+	ChartInfo   *data_manage.ChartInfoView
+	TagList     []*cross_variety.ChartTag
+	VarietyList []*cross_variety.ChartVariety
+}

+ 26 - 0
models/data_manage/cross_variety/response/tag.go

@@ -0,0 +1,26 @@
+package response
+
+import (
+	"eta/eta_api/models/data_manage/cross_variety"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// TagDeleteCheckResp 标签删除检测数据返回
+type TagDeleteCheckResp struct {
+	DeleteStatus int    `description:"检测状态:0:默认值,如果为0,继续走其他校验,1:该标签已关联图表,不允许删除!"`
+	TipsMsg      string `description:"提示信息"`
+}
+
+// TagListResp
+// @Description: 标签列表数据
+type TagListResp struct {
+	List   []*cross_variety.ChartTagItem `description:"列表数据"`
+	Paging *paging.PagingItem
+}
+
+// VarietyEdbListResp
+// @Description: 品种与关系的数据返回
+type VarietyEdbListResp struct {
+	List   []*cross_variety.ChartTagVarietyItem `description:"列表数据"`
+	Paging *paging.PagingItem
+}

+ 19 - 0
models/data_manage/cross_variety/response/variety.go

@@ -0,0 +1,19 @@
+package response
+
+import (
+	"eta/eta_api/models/data_manage/cross_variety"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// VarietyDeleteCheckResp 品种删除检测数据返回
+type VarietyDeleteCheckResp struct {
+	DeleteStatus int    `description:"检测状态:0:默认值,如果为0,继续走其他校验,1:已关联图表的品种,不允许删除!"`
+	TipsMsg      string `description:"提示信息"`
+}
+
+// VarietyListResp
+// @Description: 品种列表数据
+type VarietyListResp struct {
+	List   []*cross_variety.ChartVariety `description:"列表数据"`
+	Paging *paging.PagingItem
+}

+ 24 - 0
models/data_manage/edb_data_base.go

@@ -227,6 +227,23 @@ func GetEdbDataAllByEdbCode(edbCode string, source, subSource, limit int) (items
 	return
 }
 
+func GetBaseIndexInfoByEdbCode(edbCode string, source int) (item *BaseIndexInfo, err error) {
+	var pars []interface{}
+	pars = append(pars, edbCode)
+	o := orm.NewOrmUsingDB("data")
+
+	tableName := GetBaseIndexTableName(source)
+	if tableName == "" {
+		err = fmt.Errorf("未找到对应的表")
+		return
+	}
+	sql := ` SELECT * FROM %s WHERE index_code=? `
+
+	sql = fmt.Sprintf(sql, tableName)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
 func GetEdbDataBaseByCodeAndDate(source, subSource int, edbCode string, startDate string) (count int, err error) {
 	o := orm.NewOrmUsingDB("data")
 	tableName := GetEdbDataTableName(source, subSource)
@@ -236,6 +253,13 @@ func GetEdbDataBaseByCodeAndDate(source, subSource int, edbCode string, startDat
 	return
 }
 
+func GetBaseIndexTableName(source int) (tableName string) {
+	edbSource := EdbSourceIdMap[source]
+	if edbSource != nil {
+		tableName = edbSource.IndexTableName
+	}
+	return
+}
 func GetEdbDataAllByEdbCodeAndSubSource(edbCode string, source, subSource, limit int) (items []*EdbInfoSearchData, err error) {
 	var pars []interface{}
 	pars = append(pars, edbCode)

+ 19 - 6
models/data_manage/edb_info.go

@@ -36,8 +36,9 @@ type EdbInfo struct {
 	CalculateFormula string  `description:"计算公式"`
 	EdbType          int     `description:"指标类型:1:基础指标,2:计算指标"`
 	Sort             int     `description:"排序字段"`
-	LatestDate       string  `description:"数据最新日期"`
-	LatestValue      float64 `description:"数据最新值"`
+	LatestDate       string  `description:"数据最新日期(实际日期)"`
+	LatestValue      float64 `description:"数据最新值(实际值)"`
+	EndValue         float64 `description:"数据的最新值(预测日期的最新值)"`
 	MoveType         int     `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency    string  `description:"移动频度"`
 	NoUpdate         int8    `description:"是否停止更新,0:继续更新;1:停止更新"`
@@ -46,6 +47,8 @@ type EdbInfo struct {
 	Calendar         string  `description:"公历/农历" orm:"default(公历);"`
 	DataDateType     string  `orm:"column(data_date_type);size(255);null;default(交易日)"`
 	ManualSave       int     `description:"是否有手动保存过上下限: 0-否; 1-是"`
+	EmptyType        int     `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int     `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	TerminalCode     string  `description:"终端编码,用于配置在机器上"`
 	DataUpdateTime   string  `description:"最近一次数据发生变化的时间"`
 	ErDataUpdateDate string  `description:"本次更新,数据发生变化的最早日期"`
@@ -121,6 +124,12 @@ type EdbInfoSearchData struct {
 	Value    float64 `description:"数据"`
 }
 
+type BaseIndexInfo struct {
+	IndexName string `description:"数据日期"`
+	Unit      string `description:"单位"`
+	Frequency string `description:"频率"`
+}
+
 type EdbInfoSearchResp struct {
 	SearchItem      *EdbInfoSearch `description:"指标分类"`
 	Status          int            `description:"1:数据已存在于弘则数据库,2:新数据,3:数据已存在于弘则数据库,但是当前账号无权限"`
@@ -292,8 +301,9 @@ type EdbInfoList struct {
 	UnitEn           string                  `description:"英文单位"`
 	StartDate        string                  `description:"起始日期"`
 	EndDate          string                  `description:"终止日期"`
-	LatestDate       string                  `description:"数据最新日期"`
-	LatestValue      float64                 `description:"数据最新值"`
+	LatestDate       string                  `description:"数据最新日期(实际日期)"`
+	LatestValue      float64                 `description:"数据最新值(实际值)"`
+	EndValue         float64                 `description:"数据的最新值(预测日期的最新值)"`
 	ClassifyId       int                     `description:"分类id"`
 	UniqueCode       string                  `description:"指标唯一编码"`
 	SysUserId        int                     `description:"创建人id"`
@@ -311,6 +321,8 @@ type EdbInfoList struct {
 	IsEnEdb          bool                    `description:"是否展示英文标识"`
 	DataInsertConfig EdbDataInsertConfigItem `description:"指标数据插入配置"`
 	DataDateType     string                  `description:"数据日期类型,枚举值:交易日、自然日"`
+	EmptyType        int                     `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int                     `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	SubSource        int                     `description:"子数据来源:0:经济数据库,1:日期序列"`
 	SubSourceName    string                  `description:"子数据来源名称"`
 	IndicatorCode    string                  `description:"指标代码"`
@@ -1317,12 +1329,13 @@ type EdbInfoView struct {
 	EdbType          int     `description:"指标类型:1:基础指标,2:计算指标"`
 	Sort             int     `description:"排序字段"`
 	IsUpdate         int     `description:"当天是否已更新,1:未更新,2:已更新"`
-	LatestDate       string  `description:"数据最新日期"`
-	LatestValue      float64 `description:"数据最新值"`
+	LatestDate       string  `description:"数据最新日期(实际日期)"`
+	LatestValue      float64 `description:"数据最新值(实际值)"`
 	SubSource        int     `description:"子数据来源:0:经济数据库,1:日期序列"`
 	SubSourceName    string  `description:"子数据来源名称"`
 	IndicatorCode    string  `description:"指标代码"`
 	StockCode        string  `description:"证券代码"`
+	EndValue         float64 `description:"数据的最新值(预测日期的最新值)"`
 }
 
 // 获取指标的所有计算指标,以及计算指标所依赖计算指标

+ 6 - 0
models/data_manage/edb_info_calculate.go

@@ -18,6 +18,8 @@ type EdbInfoCalculateSaveReq struct {
 	ClassifyId       int              `description:"分类id"`
 	CalculateFormula string           `description:"计算公式"`
 	Calendar         string           `description:"公历/农历"`
+	EmptyType        int              `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int              `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EdbInfoIdArr     []EdbInfoFromTag `description:"指标信息"`
 }
 
@@ -107,6 +109,8 @@ type EdbInfoCalculateEditReq struct {
 	Unit             string           `description:"单位"`
 	ClassifyId       int              `description:"分类id"`
 	CalculateFormula string           `description:"计算公式"`
+	EmptyType        int              `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int              `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EdbInfoIdArr     []EdbInfoFromTag `description:"指标信息"`
 }
 
@@ -200,6 +204,8 @@ type EdbInfoCalculateBatchSaveReqByEdbLib struct {
 	MoveFrequency    string           `description:"移动频度:天/周/月/季/年"`
 	Calendar         string           `description:"公历/农历"`
 	Data             interface{}      `description:"数据列"`
+	EmptyType        int              `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int              `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 }
 
 // EdbInfoCalculateBatchEditReqByEdbLib 编辑计算指标的请求参数

+ 1 - 0
models/data_manage/edb_source.go

@@ -23,6 +23,7 @@ type EdbSource struct {
 	BridgeFlag       string `description:"桥接服务对象标识"`
 	SourceExtend     string `description:"扩展字段做查询用"`
 	EdbCodeRequired  int    `description:"指标编码是否必填: 0-否; 1-是"`
+	IndexTableName   string `description:"数据源指标表名"`
 }
 
 // GetEdbSourceItemsByCondition 获取指标来源列表

+ 1 - 1
models/data_manage/excel/excel_edb_mapping.go

@@ -100,7 +100,7 @@ func GetAllExcelEdbMappingByExcelInfoId(excelInfoId int) (items []*ExcelEdbMappi
 // DeleteCustomAnalysisExcelEdbMappingByEdbInfoId
 // @Description: 根据指标id删除与自定义分析表格的关系
 // @author: Roc
-// @datetime2023-11-02 13:20:02
+// @datetime 2023-11-02 13:20:02
 // @param excelInfoId int
 // @return err error
 func DeleteCustomAnalysisExcelEdbMappingByEdbInfoId(excelInfoId int) (err error) {

+ 16 - 0
models/data_manage/excel/excel_info.go

@@ -162,6 +162,14 @@ func GetExcelInfoById(excelInfoId int) (item *ExcelInfo, err error) {
 	return
 }
 
+// GetExcelInfoByUnicode 编码获取表格
+func GetExcelInfoByUnicode(unicode string) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE unique_code = ? AND is_delete = 0 `
+	err = o.Raw(sql, unicode).QueryRow(&item)
+	return
+}
+
 func GetExcelInfoViewById(excelInfoId int) (item *ExcelInfoView, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM excel_info WHERE excel_info_id=? AND is_delete=0 `
@@ -500,3 +508,11 @@ func SaveExcelInfoAndSheet(excelInfo *ExcelInfo, updateExcelInfoParam []string,
 
 	return
 }
+
+// BatchRefreshExcelReq 批量刷新表格请求
+type BatchRefreshExcelReq struct {
+	ExcelCodes      []string `description:"表格编码"`
+	ReportId        int      `description:"报告ID"`
+	ReportChapterId int      `description:"报告章节ID"`
+	Source          string   `description:"来源,枚举值:report、english_report、smart_report"`
+}

+ 59 - 2
models/data_manage/my_chart.go

@@ -71,13 +71,13 @@ func GetPublicChartClassifyAllExceptMy(adminId int) (item []*MyChartClassify, er
 }
 
 type MyChartClassifyResp struct {
-	List     []*MyChartClassify
+	List     []*MyChartClassifyItem
 	Language string `description:"指标的展示语言,CN:中文,EN:英文"`
 }
 
 // PublicChartClassifyResp 公共分类返回数据结构体
 type PublicChartClassifyResp struct {
-	List     []PublicChartClassifyItem
+	List     []PublicChartClassifyList
 	Language string `description:"指标的展示语言,CN:中文,EN:英文"`
 }
 
@@ -91,6 +91,13 @@ type PublicChartClassifyItem struct {
 	IsCompanyPublic     int    `description:"是否为客户可见"`
 }
 
+// PublicChartClassifyList 公共分类结构体
+type PublicChartClassifyList struct {
+	MenuAdminId int                       `description:"目录创建人ID"`
+	MenuName    string                    `description:"目录名称"`
+	Items       []PublicChartClassifyItem `description:"分类数据"`
+}
+
 type MyChartClassifyAddReq struct {
 	MyChartClassifyName string `description:"分类名称"`
 }
@@ -901,3 +908,53 @@ func GetMyChartClassifyByClassifyId(classifyId int) (item *MyChartClassify, err
 	err = o.Raw(sql, classifyId).QueryRow(&item)
 	return
 }
+
+// MyChartClassifyItem 我的图表分类信息
+type MyChartClassifyItem struct {
+	MyChartClassifyId   int    `description:"分类ID"`
+	MyChartClassifyName string `description:"分类名称"`
+	AdminId             int    `description:"创建人id"`
+	IsPublic            int    `description:"是否公共分类"`
+	IsCompanyPublic     int    `description:"是否为用户公共分类"`
+	ChartNum            int    `description:"分类下的图表数量"`
+}
+
+// FormatMyChartClassify2Item 格式化我的图表信息
+func FormatMyChartClassify2Item(origin *MyChartClassify, chartNum int) (item *MyChartClassifyItem) {
+	if origin == nil {
+		return
+	}
+	item = new(MyChartClassifyItem)
+	item.MyChartClassifyId = origin.MyChartClassifyId
+	item.MyChartClassifyName = origin.MyChartClassifyName
+	item.AdminId = origin.AdminId
+	item.IsPublic = origin.IsPublic
+	item.IsCompanyPublic = origin.IsCompanyPublic
+	item.ChartNum = chartNum
+	return
+}
+
+// MyChartClassifyIdAndNum 我的图表-分类ID及图表数
+type MyChartClassifyIdAndNum struct {
+	MyChartClassifyId int `description:"分类ID"`
+	ChartNum          int `description:"分类下的图表数量"`
+}
+
+// GetMyChartClassifyIdAndNum 我的图表-获取分类ID及图表数
+func GetMyChartClassifyIdAndNum(cond string, pars []interface{}) (items []*MyChartClassifyIdAndNum, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT
+			a.my_chart_classify_id,
+			COUNT(1) AS chart_num
+		FROM
+			my_chart_classify AS a
+		INNER JOIN my_chart_classify_mapping AS b ON a.my_chart_classify_id = b.my_chart_classify_id
+		INNER JOIN my_chart AS c ON b.my_chart_id = c.my_chart_id
+		INNER JOIN chart_info AS d ON c.chart_info_id = d.chart_info_id
+		WHERE
+			1 = 1 %s
+		GROUP BY
+			a.my_chart_classify_id`, cond)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}

+ 4 - 0
models/data_manage/predict_edb_conf.go

@@ -14,6 +14,8 @@ type PredictEdbConf struct {
 	RuleType         int       `description:"预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值,9:动态环差"`
 	FixedValue       float64   `description:"固定值"`
 	Value            string    `description:"配置的值"`
+	EmptyType        int       `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int       `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EndDate          time.Time `description:"截止日期"`
 	ModifyTime       time.Time `description:"修改时间"`
 	CreateTime       time.Time `description:"添加时间"`
@@ -27,6 +29,8 @@ type PredictEdbConfDetail struct {
 	RuleType         int                                     `description:"预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值,9:动态环差"`
 	FixedValue       float64                                 `description:"固定值"`
 	Value            string                                  `description:"配置的值"`
+	EmptyType        int                                     `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int                                     `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EndDate          time.Time                               `description:"截止日期"`
 	ModifyTime       time.Time                               `description:"修改时间"`
 	CreateTime       time.Time                               `description:"添加时间"`

+ 2 - 0
models/data_manage/predict_edb_info_calculate.go

@@ -10,6 +10,8 @@ type PredictEdbInfoCalculateSaveReq struct {
 	Unit             string           `description:"单位"`
 	ClassifyId       int              `description:"分类id"`
 	CalculateFormula string           `description:"计算公式"`
+	EmptyType        int              `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int              `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EdbInfoIdArr     []EdbInfoFromTag `description:"指标信息"`
 }
 

+ 2 - 0
models/data_manage/request/predict_edb_info.go

@@ -42,6 +42,8 @@ type AddPredictEdbInfoReq struct {
 type RuleConfig struct {
 	RuleType     int                          `description:"预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值,9:动态环差"`
 	Value        string                       `description:"值"`
+	EmptyType    int                          `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType int                          `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EndDate      string                       `description:"截止日期"`
 	EdbInfoIdArr []data_manage.EdbInfoFromTag `description:"指标信息"`
 }

+ 1 - 1
models/data_manage/response/predit_edb_info.go

@@ -46,5 +46,5 @@ type PredictEdbInfoChartDataResp struct {
 // PredictRuleCalculateByNineResp 获取预测指标规则9的绘图数据返回
 type PredictRuleCalculateByNineResp struct {
 	LatestDate string
-	DataList   interface{}
+	DataList   []*data_manage.EdbDataList
 }

+ 36 - 0
models/db.go

@@ -1,8 +1,10 @@
 package models
 
 import (
+	"eta/eta_api/models/aimod"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/cross_variety"
 	"eta/eta_api/models/data_manage/excel"
 	future_good2 "eta/eta_api/models/data_manage/future_good"
 	"eta/eta_api/models/data_manage/supply_analysis"
@@ -68,6 +70,15 @@ func init() {
 		weeklyDb.SetConnMaxLifetime(10 * time.Minute)
 	}
 
+	if utils.MYSQL_AI_URL != "" {
+		_ = orm.RegisterDataBase("ai", "mysql", utils.MYSQL_AI_URL)
+		orm.SetMaxIdleConns("ai", 50)
+		orm.SetMaxOpenConns("ai", 100)
+
+		weeklyDb, _ := orm.GetDB("ai")
+		weeklyDb.SetConnMaxLifetime(10 * time.Minute)
+	}
+
 	orm.Debug = true
 	orm.DebugLog = orm.NewLog(utils.Binlog)
 
@@ -157,6 +168,12 @@ func init() {
 	// 初始化EXCEL的表
 	initExcel()
 
+	// 初始化跨品种分析表
+	initCrossVariety()
+
+	//初始化AI
+	initAi()
+
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
 	data_manage.InitEdbSourceVar()
 }
@@ -468,5 +485,24 @@ func initSmartReport() {
 	orm.RegisterModel(
 		new(smart_report.SmartReport),        // 智能研报主表
 		new(smart_report.SmartReportSaveLog), // 智能研报-保存记录表
+		new(smart_report.SmartReportResource), // 智能研报-资源表
+	)
+}
+
+// initCrossVariety 跨品种分析
+func initCrossVariety() {
+	orm.RegisterModel(
+		new(cross_variety.ChartVariety),          // 品种表
+		new(cross_variety.ChartTag),              // 标签表
+		new(cross_variety.ChartTagVariety),       // 标签、品种、指标关系表
+		new(cross_variety.ChartVarietyMapping),   // 图表与品种的关系表
+		new(cross_variety.ChartInfoCrossVariety), // 跨品种分析配置表
+	)
+}
+
+func initAi() {
+	orm.RegisterModel(
+		new(aimod.AiChatTopic),
+		new(aimod.AiChat),
 	)
 }

+ 16 - 3
models/ppt_english/ppt_english.go

@@ -344,10 +344,23 @@ type EnglishPPT2ReportReq struct {
 	Abstract         string `description:"摘要"`
 }
 
-
-func GetPptEnglishByTitleAndId(title string,adminId int) (item *PptEnglish, err error) {
+func GetPptEnglishByTitleAndId(title string, adminId int) (item *PptEnglish, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `SELECT * FROM ppt_english WHERE 1=1 AND title=? AND admin_id=? `
 	err = o.Raw(sql, title, adminId).QueryRow(&item)
 	return
-}
+}
+
+// PPTEditingCache PPT编辑缓存信息
+type PPTEditingCache struct {
+	IsEditing bool   `description:"是否有人编辑"`
+	AdminId   int    `description:"编辑者ID"`
+	Editor    string `description:"编辑者姓名"`
+	Tips      string `description:"提示信息"`
+}
+
+// EnglishPPTDetailResp 英文PPT详情响应体
+type EnglishPPTDetailResp struct {
+	*PptEnglish
+	Editor PPTEditingCache `description:"编辑人信息"`
+}

+ 15 - 2
models/ppt_v2.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_api/models/ppt_english"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
@@ -285,9 +286,21 @@ type BatchEnPPT2CnReq struct {
 	GroupId int    `description:"目录ID"`
 }
 
-func GetPptV2ByTitleAndId(title string,adminId int) (item *PptV2, err error) {
+func GetPptV2ByTitleAndId(title string, adminId int) (item *PptV2, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `SELECT * FROM ppt_v2 WHERE 1=1 AND title=? AND admin_id=? `
 	err = o.Raw(sql, title, adminId).QueryRow(&item)
 	return
-}
+}
+
+// PPTEditingReq 标记编辑中请求体
+type PPTEditingReq struct {
+	PptId  int `description:"PPT主键ID"`
+	Status int `description:"标记状态: 1-编辑中; 2-编辑完成"`
+}
+
+// PPTDetailResp PPT详情响应体
+type PPTDetailResp struct {
+	*PptV2
+	Editor ppt_english.PPTEditingCache `description:"编辑人信息"`
+}

+ 1 - 0
models/sandbox/request/sandbox.go

@@ -32,6 +32,7 @@ type AddAndEditSandboxV2 struct {
 	SandboxId         int    `description:"沙盘id"`
 	Name              string `description:"沙盘名称"`
 	Content           string `description:"沙盘内容"`
+	Style             int    `description:"风格"`
 	MindmapData       string `description:"思维导图内容"`
 	PicUrl            string `description:"沙盘图片地址"`
 	SvgData           string `description:"沙盘svg图片数据"`

+ 1 - 0
models/sandbox/sandbox.go

@@ -42,6 +42,7 @@ type Sandbox struct {
 	CreateTime        time.Time `description:"创建时间"`
 	SandboxClassifyId int       `description:"分类id"`
 	Sort              int       `description:"排序"`
+	Style             int       `description:"风格"`
 }
 
 // Update 沙盘字段变更

+ 35 - 4
models/sandbox/sandbox_classify.go

@@ -55,6 +55,8 @@ type SandboxClassifyItems struct {
 	Level               int       `description:"层级"`
 	Sort                int       `description:"排序字段,越小越靠前,默认值:10"`
 	SandboxId           int       `description:"沙盘id"`
+	ChartPermissionId   int       `description:"品种id"`
+	ChartPermissionName string    `description:"品种名称"`
 	Children            []*SandboxClassifyItems
 }
 
@@ -88,6 +90,8 @@ func GetSandboxClassifyMaxSort(parentId int) (sort int, err error) {
 type EditSandboxClassifyReq struct {
 	SandboxClassifyName string `description:"分类名称"`
 	SandboxClassifyId   int    `description:"分类id"`
+	ChartPermissionId   int    `description:"品种id"`
+	ChartPermissionName string `description:"品种名称"`
 }
 
 func GetSandboxClassifyById(classifyId int) (item *SandboxClassify, err error) {
@@ -97,10 +101,10 @@ func GetSandboxClassifyById(classifyId int) (item *SandboxClassify, err error) {
 	return
 }
 
-func EditSandboxClassify(classifyId int, sandboxClassifyName string) (err error) {
+func EditSandboxClassify(classifyId, ChartPermissionId int, sandboxClassifyName, ChartPermissionName string) (err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := `UPDATE sandbox_classify SET sandbox_classify_name=?,modify_time=NOW() WHERE sandbox_classify_id=? `
-	_, err = o.Raw(sql, sandboxClassifyName, classifyId).Exec()
+	sql := `UPDATE sandbox_classify SET sandbox_classify_name=?,chart_permission_id = ?, chart_permission_name = ?, modify_time=NOW() WHERE sandbox_classify_id=? `
+	_, err = o.Raw(sql, sandboxClassifyName, ChartPermissionId, ChartPermissionName, classifyId).Exec()
 	return
 }
 
@@ -205,6 +209,8 @@ func GetSandboxClassifyAndInfoByParentId(parentId int) (items []*SandboxClassify
 	sys_user_real_name AS sys_user_name,
 	sort,
 	level,
+	chart_permission_id,
+	chart_permission_name,
 	0 AS is_delete
 FROM
 	sandbox_classify 
@@ -221,6 +227,8 @@ SELECT
 	sys_user_name,
 	sort,
 	0 AS level,
+	chart_permission_id,
+	chart_permission_name,
 	is_delete 
 FROM
 	sandbox 
@@ -239,9 +247,32 @@ type SandboxLinkCheckReq struct {
 	ReportIdList    []int `description:"报告id列表"`
 }
 
-
 type SandboxLinkCheckResp struct {
 	EdbInfoIdList   []int `description:"指标id列表"`
 	ChartInfoIdList []int `description:"图库id列表"`
 	ReportIdList    []int `description:"报告id列表"`
+}
+
+// 获取所有子级分类id
+func GetSandboxClassifySubcategories(classifyId int) (Ids string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT GROUP_CONCAT(sandbox_classify_id) AS ids
+FROM (
+SELECT @pv := ? AS sandbox_classify_id
+UNION ALL
+SELECT sc.sandbox_classify_id
+FROM sandbox_classify sc
+JOIN (SELECT @pv := ?) initial
+WHERE sc.parent_id = @pv
+) subcategories; `
+	err = o.Raw(sql, classifyId, classifyId).QueryRow(&Ids)
+	return
+}
+
+// UpdateSandboxClassifyChartPermissionById 根据沙盘id更新品种
+func UpdateSandboxClassifyChartPermissionById(ChartPermissionId int, ChartPermissionName, Ids string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` UPDATE sandbox_classify SET chart_permission_id = ?, chart_permission_name = ? WHERE sandbox_classify_id IN ( `+Ids +` ) `
+	_, err = o.Raw(sql, ChartPermissionId, ChartPermissionName).Exec()
+	return
 }

+ 18 - 0
models/smart_report/smart_report.go

@@ -53,6 +53,9 @@ type SmartReport struct {
 	DetailPdfUrl        string    `description:"报告详情PDF地址"`
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	CanvasColor         string    `description:"画布颜色"`
 }
 
 func (m *SmartReport) TableName() string {
@@ -206,6 +209,9 @@ type SmartReportItem struct {
 	ModifyTime          string  `description:"修改时间"`
 	CanEdit             bool    `description:"是否可编辑"`
 	Editor              string  `description:"当前编辑人"`
+	HeadImg             string  `description:"报告头图地址"`
+	EndImg              string  `description:"报告尾图地址"`
+	CanvasColor         string  `description:"画布颜色"`
 }
 
 // FormatSmartReport2Item 格式化智能研报数据格式
@@ -249,6 +255,9 @@ func FormatSmartReport2Item(origin *SmartReport) (item *SmartReportItem) {
 	item.DetailPdfUrl = origin.DetailPdfUrl
 	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
 	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	item.HeadImg = origin.HeadImg
+	item.EndImg = origin.EndImg
+	item.CanvasColor = origin.CanvasColor
 	return
 }
 
@@ -263,6 +272,9 @@ type SmartReportAddReq struct {
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
 	Frequency          string `description:"频度"`
+	HeadImg            string `description:"报告头图地址"`
+	EndImg             string `description:"报告尾图地址"`
+	CanvasColor        string `description:"画布颜色"`
 }
 
 // SmartReportEditReq 编辑智能研报请求体
@@ -271,6 +283,9 @@ type SmartReportEditReq struct {
 	SmartReportId int    `description:"智能研报ID"`
 	Content       string `description:"内容"`
 	ContentStruct string `description:"内容结构"`
+	HeadImg       string `description:"报告头图地址"`
+	EndImg        string `description:"报告尾图地址"`
+	CanvasColor   string `description:"画布颜色"`
 }
 
 // SmartReportRemoveReq 删除智能研报请求体
@@ -297,6 +312,9 @@ type SmartReportSaveContentReq struct {
 	Content       string `description:"内容"`
 	ContentStruct string `description:"内容结构"`
 	NoChange      int    `description:"内容是否未改变:1:内容未改变"`
+	HeadImg       string `description:"报告头图地址"`
+	EndImg        string `description:"报告尾图地址"`
+	CanvasColor   string `description:"画布颜色"`
 }
 
 // SmartReportSaveContentResp 保存草稿响应体

+ 107 - 0
models/smart_report/smart_resource.go

@@ -0,0 +1,107 @@
+package smart_report
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+type SmartReportResource struct {
+	ResourceId int       `orm:"column(resource_id);pk" description:"智能研报资源ID"`
+	ImgUrl     string    // 图片链接
+	ImgName    string    // 图片名称
+	Type       int       // 类型 1-版头 2-版尾
+	CreateTime time.Time // 创建时间
+}
+
+func (m *SmartReportResource) TableName() string {
+	return "smart_report_resource"
+}
+
+func (m *SmartReportResource) PrimaryId() string {
+	return "resource_id"
+}
+
+func (m *SmartReportResource) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ResourceId = int(id)
+	return
+}
+
+func (m *SmartReportResource) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SmartReportResource) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ResourceId).Exec()
+	return
+}
+
+type SmartReportResourceItem struct {
+	ResourceId int    `orm:"column(resource_id);pk" description:"智能研报资源ID"`
+	ImgUrl     string // 图片链接
+	ImgName    string // 图片名称
+	Type       int    // 类型 1-版头 2-版尾
+	CreateTime string // 创建时间
+}
+
+// SmartReportResourceListResp 智能研报资源库
+type SmartReportResourceListResp struct {
+	List   []*SmartReportResourceItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+func (m *SmartReportResource) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *SmartReportResource) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, startSize, pageSize int) (items []*SmartReportResourceItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := ` ORDER BY create_time DESC`
+
+	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
+}
+
+// SmartReportRenameReq 智能研报资源重命名请求体
+type SmartReportRenameReq struct {
+	ResourceId int    `description:"资源ID"`
+	ImgName    string `description:"图片名称"`
+}
+
+func (m *SmartReportResource) GetItemById(id int) (item *SmartReportResource, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+// SmartReportResourceRemoveReq 删除智能研报资源请求体
+type SmartReportResourceRemoveReq struct {
+	ResourceIds string `description:"资源IDs"`
+}
+
+// SmartReportResourceAddReq 新增智能研报资源请求体
+type SmartReportResourceAddReq struct {
+	Type    int    `description:"类型 1-版头 2-版尾"`
+	ImgUrl  string `description:"图片链接"`
+	ImgName string `description:"图片名称"`
+}

+ 423 - 0
routers/commentsRouter.go

@@ -7,6 +7,51 @@ import (
 
 func init() {
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/chat`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "TopicDelete",
+            Router: `/topic/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "TopicDetail",
+            Router: `/topic/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "TopicEdit",
+            Router: `/topic/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "TopicList",
+            Router: `/topic/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"],
         beego.ControllerComments{
             Method: "AddChartClassify",
@@ -178,6 +223,267 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "DeleteChart",
+            Router: `/chart_info/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "EnInfoEdit",
+            Router: `/chart_info/en/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/chart_info/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "Relation",
+            Router: `/chart_info/relation`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ChartInfoController"],
+        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/cross_variety:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassify",
+            Router: `/classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassifyCheck",
+            Router: `/classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"],
+        beego.ControllerComments{
+            Method: "EditChartClassify",
+            Router: `/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:ClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyMove",
+            Router: `/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/tag/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/tag/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "DeleteCheck",
+            Router: `/tag/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/tag/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/tag/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "VarietyEdbList",
+            Router: `/tag/variety_edb/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:TagController"],
+        beego.ControllerComments{
+            Method: "SaveVarietyEdb",
+            Router: `/tag/variety_edb/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/variety/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/variety/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"],
+        beego.ControllerComments{
+            Method: "DeleteCheck",
+            Router: `/variety/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/variety/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/cross_variety:VarietyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/variety/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:CustomAnalysisController"],
         beego.ControllerComments{
             Method: "Add",
@@ -421,6 +727,24 @@ 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: "BatchRefresh",
+            Router: `/excel_info/table/batch_refresh`,
+            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: "GetBatchChartRefreshResult",
+            Router: `/excel_info/table/batch_refresh/result`,
+            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: "Calculate",
@@ -3202,6 +3526,51 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "YongyiClassify",
+            Router: `/yongyi/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "ExportYongyiList",
+            Router: `/yongyi/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "YongyiIndexData",
+            Router: `/yongyi/index/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "YongyiSearchList",
+            Router: `/yongyi/search_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "YongyiSingleData",
+            Router: `/yongyi/single_data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:JiaYueEdbSourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:JiaYueEdbSourceController"],
         beego.ControllerComments{
             Method: "FrequencyList",
@@ -5083,6 +5452,42 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/resource/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/resource/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/resource/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportResourceController"],
+        beego.ControllerComments{
+            Method: "Rename",
+            Router: `/resource/rename`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
         beego.ControllerComments{
             Method: "GetClassifyName",
@@ -5470,6 +5875,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:PptEnglishController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:PptEnglishController"],
+        beego.ControllerComments{
+            Method: "Editing",
+            Router: `/editing`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:PptEnglishController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:PptEnglishController"],
         beego.ControllerComments{
             Method: "Grant",
@@ -5758,6 +6172,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:PptV2Controller"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:PptV2Controller"],
+        beego.ControllerComments{
+            Method: "Editing",
+            Router: `/editing`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:PptV2Controller"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:PptV2Controller"],
         beego.ControllerComments{
             Method: "Grant",

+ 16 - 0
routers/router.go

@@ -9,8 +9,10 @@ package routers
 
 import (
 	"eta/eta_api/controllers"
+	"eta/eta_api/controllers/ai"
 	"eta/eta_api/controllers/data_manage"
 	"eta/eta_api/controllers/data_manage/correlation"
+	"eta/eta_api/controllers/data_manage/cross_variety"
 	"eta/eta_api/controllers/data_manage/excel"
 	future_good2 "eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
@@ -310,6 +312,20 @@ func init() {
 		web.NSNamespace("/smart_report",
 			web.NSInclude(
 				&smart_report.SmartReportController{},
+				&smart_report.SmartReportResourceController{},
+			),
+		),
+		web.NSNamespace("/ai",
+			web.NSInclude(
+				&ai.AiController{},
+			),
+		),
+		web.NSNamespace("/cross_variety", //跨品种分析
+			web.NSInclude(
+				&cross_variety.ClassifyController{},
+				&cross_variety.ChartInfoController{},
+				&cross_variety.VarietyController{},
+				&cross_variety.TagController{},
 			),
 		),
 	)

+ 69 - 0
services/aiser/ai.go

@@ -0,0 +1,69 @@
+package aiser
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/utils"
+	"io/ioutil"
+	"net/http"
+	"strings"
+	"time"
+)
+
+func ChatAutoMsg(prompt string) (result string, err error) {
+	chatUrl := utils.EtaAiUrl + `chat/auto_msg`
+
+	param := make(map[string]interface{})
+	param["Prompt"] = prompt
+	postData, err := json.Marshal(param)
+	if err != nil {
+		return result, err
+	}
+
+	client := &http.Client{}
+	//提交请求
+	reqest, err := http.NewRequest("POST", chatUrl, strings.NewReader(string(postData)))
+	businessCode := utils.BusinessCode
+	if businessCode == "" {
+		return businessCode, errors.New("未获取到商户号")
+	}
+	nonce := utils.GetRandStringNoSpecialChar(16)
+	timestamp := time.Now().Format(utils.FormatDateTimeUnSpace)
+	signature := utils.GetSign(nonce, timestamp, utils.EtaAppid, utils.EtaSecret)
+	//增加header选项
+	reqest.Header.Add("BusinessCode", businessCode)
+	reqest.Header.Add("Nonce", nonce)
+	reqest.Header.Add("Timestamp", timestamp)
+	reqest.Header.Add("Appid", utils.EtaAppid)
+	reqest.Header.Add("Signature", signature)
+	reqest.Header.Set("Content-Type", "application/json")
+
+	utils.FileLog.Info("postData:" + string(postData))
+
+	response, err := client.Do(reqest)
+	if err != nil {
+		return
+	}
+	defer response.Body.Close()
+
+	body, err := ioutil.ReadAll(response.Body)
+
+	utils.FileLog.Info("result:" + string(body))
+
+	resp := new(ChatAutoMsgResp)
+	err = json.Unmarshal(body, &resp)
+	if err != nil {
+		return result, err
+	}
+	if resp.Ret != 200 {
+		return resp.Msg, nil
+	}
+	result = resp.Data
+	return result, nil
+}
+
+type ChatAutoMsgResp struct {
+	Ret  int
+	Data string
+	Msg  string
+}

+ 22 - 0
services/data/base_edb_lib.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/response"
 	"eta/eta_api/utils"
 	"fmt"
 	"io/ioutil"
@@ -131,6 +132,27 @@ func SaveBasePredictEdbData(edbInfoCalculateBatchSaveReqStr string) (resp *AddPr
 	return
 }
 
+type PredictRuleCalculateByNineRespResponse struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        response.PredictRuleCalculateByNineResp
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+// PredictCalculateByNinePreview 预测指标动态环差演算
+func PredictCalculateByNinePreview(RuleConfigReqStr string) (resp *PredictRuleCalculateByNineRespResponse, err error) {
+	_, resultByte, err := postAddEdbData(RuleConfigReqStr, "predict/calculate_by_nine/preview")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
 // SavePredictEdbData 新增/编辑预测指标运算
 func SavePredictEdbData(edbInfoCalculateBatchSaveReqStr string) (resp *AddPredictEdbDataResponse, err error) {
 	_, resultByte, err := postAddEdbData(edbInfoCalculateBatchSaveReqStr, "predict_calculate/save")

+ 26 - 0
services/data/chart_classify.go

@@ -141,3 +141,29 @@ func GetChartOpButton(sysUser *system.Admin, belongUserId int) (button data_mana
 
 	return
 }
+
+// HandleNoPermissionChart 图表列表返回,将没有权限的图表移除
+func HandleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*data_manage.ChartClassifyItems) {
+	// 移除没有权限的图表
+	newAllNodes = make([]*data_manage.ChartClassifyItems, 0)
+	for _, node := range allNodes {
+		// 二级分类
+		tmpNodeInfo := *node
+		tmpNodeList := make([]*data_manage.ChartClassifyItems, 0)
+
+		if node.Children != nil {
+			for _, chartInfo := range node.Children {
+				// 如果指标不可见,那么就不返回该指标
+				if _, ok := noPermissionChartIdMap[chartInfo.ChartInfoId]; ok {
+					continue
+				}
+				tmpNodeList = append(tmpNodeList, chartInfo)
+			}
+		}
+
+		tmpNodeInfo.Children = tmpNodeList
+		newAllNodes = append(newAllNodes, &tmpNodeInfo)
+	}
+
+	return
+}

+ 2 - 2
services/data/chart_info.go

@@ -508,7 +508,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		}
 		dataList := make([]*data_manage.EdbDataList, 0)
 		//fmt.Println("chart:", v.Source, v.EdbInfoId, startDateReal, endDate)
-		fmt.Println("calendarPreYear:", calendarPreYear)
+		//fmt.Println("calendarPreYear:", calendarPreYear)
 		//var newEdbInfo *data_manage.EdbInfo
 		switch v.EdbInfoCategoryType {
 		case 0:
@@ -1230,7 +1230,7 @@ func ChartInfoRefreshV2(chartInfoId int) (err error, isAsync bool) {
 	}
 
 	// 批量刷新
-	err, isAsync = EdbInfoRefreshAllFromBaseV3(edbIdList, false, false)
+	err, isAsync = EdbInfoRefreshAllFromBaseV3(edbIdList, false, false, false)
 
 	if err != nil {
 		return

+ 1 - 1
services/data/correlation/chart_info.go

@@ -578,7 +578,7 @@ func ChartInfoRefresh(chartInfoId int) (err error) {
 	}
 
 	// 批量刷新ETA指标
-	err, _ = data.EdbInfoRefreshAllFromBaseV3([]int{correlationChart.EdbInfoIdFirst, correlationChart.EdbInfoIdSecond}, false, true)
+	err, _ = data.EdbInfoRefreshAllFromBaseV3([]int{correlationChart.EdbInfoIdFirst, correlationChart.EdbInfoIdSecond}, false, true, false)
 	if err != nil {
 		return
 	}

+ 962 - 0
services/data/cross_variety/chart.go

@@ -0,0 +1,962 @@
+package cross_variety
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage"
+	cross_varietyModel "eta/eta_api/models/data_manage/cross_variety"
+	"eta/eta_api/models/data_manage/cross_variety/request"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ChartInfoResp 截面散点图数据
+type ChartInfoResp struct {
+	XName       string                         `description:"x轴名称"`
+	XNameEn     string                         `description:"x轴名称(英文)"`
+	XUnitName   string                         `description:"x轴单位名称"`
+	XUnitNameEn string                         `description:"x轴单位名称(英文)"`
+	YName       string                         `description:"y轴名称"`
+	YNameEn     string                         `description:"y轴名称(英文)"`
+	YUnitName   string                         `description:"y轴单位名称"`
+	YUnitNameEn string                         `description:"y轴单位名称(英文)"`
+	XMinValue   string                         `description:"X轴的最小值"`
+	XMaxValue   string                         `description:"X轴的最大值"`
+	YMinValue   string                         `description:"Y轴的最小值"`
+	YMaxValue   string                         `description:"Y轴的最大值"`
+	DataList    []SectionScatterSeriesItemResp `description:"数据列"`
+}
+
+// SectionScatterSeriesItemResp 系列的返回
+type SectionScatterSeriesItemResp struct {
+	Name                string            `description:"系列名"`
+	NameEn              string            `description:"系列名(英文)"`
+	Color               string            `description:"颜色"`
+	CoordinatePointData []CoordinatePoint `description:"趋势线的前后坐标点"`
+}
+
+// SectionScatterEdbItemResp 截面散点的返回参数
+type SectionScatterEdbItemResp struct {
+	XEdbInfoId int     `description:"X轴指标id"`
+	XDate      string  `description:"X轴指标实际日期"`
+	XName      string  `description:"X轴指标名称"`
+	XNameEn    string  `description:"X轴指标英文名称"`
+	XValue     float64 `description:"X轴实际值"`
+	YEdbInfoId int     `description:"Y轴指标id"`
+	YDate      string  `description:"Y轴指标实际日期"`
+	YName      string  `description:"Y轴指标名称"`
+	YNameEn    string  `description:"Y轴指标英文名称"`
+	YValue     float64 `description:"Y轴实际值"`
+	IsShow     bool    `description:"是否展示"`
+	Name       string  `description:"标签名称"`
+	NameEn     string  `description:"英文标签名称"`
+}
+
+// CoordinatePoint 坐标点
+type CoordinatePoint struct {
+	X          float64
+	Y          float64
+	XEdbInfoId int
+	YEdbInfoId int
+	XDate      string
+	YDate      string
+}
+
+// GetChartData
+// @Description: 获取跨品种分析图表数据
+// @author: Roc
+// @datetime 2023-11-24 09:42:59
+// @param chartInfoId int
+// @param config request.ChartConfigReq
+// @return edbList []*data_manage.ChartEdbInfoMapping
+// @return dataResp ChartInfoResp
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*data_manage.ChartEdbInfoMapping, dataResp ChartInfoResp, err error, errMsg string, isSendEmail bool) {
+	moveUnitDays, ok := utils.FrequencyDaysMap[config.CalculateUnit]
+	if !ok {
+		errMsg = "错误的分析周期"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	isSendEmail = true
+
+	// 品种map
+	varietyMap := make(map[int]*cross_varietyModel.ChartVariety)
+	{
+		varietyList, tmpErr := cross_varietyModel.GetVarietyListByIdList(config.VarietyList)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		for _, v := range varietyList {
+			varietyMap[v.ChartVarietyId] = v
+		}
+	}
+
+	// 标签m
+	var xTagInfo, yTagInfo *cross_varietyModel.ChartTag
+	{
+		tagList, tmpErr := cross_varietyModel.GetTagListByIdList([]int{config.TagX, config.TagY})
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		for _, v := range tagList {
+			if v.ChartTagId == config.TagX {
+				xTagInfo = v
+			} else if v.ChartTagId == config.TagY {
+				yTagInfo = v
+			}
+		}
+	}
+	if xTagInfo == nil {
+		errMsg = "找不到对应的X轴标签"
+		err = errors.New(errMsg)
+		return
+	}
+	if yTagInfo == nil {
+		errMsg = "找不到对应的Y轴标签"
+		err = errors.New(errMsg)
+		return
+	}
+
+	xVarietyEdbMap, yVarietyEdbMap, edbInfoIdList, err := GetXYEdbIdList(config.TagX, config.TagY, config.VarietyList)
+	if err != nil {
+		return
+	}
+
+	if len(edbInfoIdList) <= 0 {
+		errMsg = "品种未配置指标"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdList)
+	if err != nil {
+		errMsg = "获取指标信息失败"
+		err = errors.New("获取指标信息失败,ERR:" + err.Error())
+		return
+	}
+
+	// 指标对应的所有数据
+	chartType := 1 //1:普通图,2:季节性图
+	calendar := "公历"
+	edbDataListMap, edbList, err := data.GetEdbDataMapList(chartInfoId, chartType, calendar, "", "", mappingList, "")
+	if err != nil {
+		return
+	}
+
+	currDay := time.Now()
+	currDay = time.Date(currDay.Year(), currDay.Month(), currDay.Day(), 0, 0, 0, 0, time.Local)
+
+	dataMap := make(map[string]float64)
+	dateMap := make(map[string]string)
+	for dateIndex, dateConfig := range config.DateConfigList {
+		for _, edbInfoMapping := range mappingList {
+			// 数据会是正序的
+			dataList, ok := edbDataListMap[edbInfoMapping.EdbInfoId]
+			if !ok {
+				continue
+			}
+
+			lenData := len(dataList)
+			if lenData <= 0 {
+				continue
+			}
+			// 数据的开始索引
+			k := lenData - 1
+
+			// 数据的最晚日期
+			dataEndDateStr := dataList[k].DataTime
+			dataEndDate, tmpErr := time.ParseInLocation(utils.FormatDate, dataEndDateStr, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			// 数据开始日期
+			endDateStr := ``
+			var endDate time.Time
+
+			var currVal float64
+			switch dateConfig.DateType {
+			case 1: // 1:最新日期;
+				endDateStr = dataEndDateStr
+				endDate = dataEndDate
+				currVal = dataList[k].Value
+			case 2: // 2:N天前
+				tmpEndDate := dataEndDate.AddDate(0, 0, -dateConfig.Num)
+				tmpEndDateStr := tmpEndDate.Format(utils.FormatDate)
+
+				for i := k; i >= 0; i-- {
+					tmpDateStr := dataList[i].DataTime
+					// 如果正好是这一天,那么就直接break了
+					if tmpEndDateStr == tmpDateStr {
+						k = i
+						endDateStr = tmpDateStr
+						endDate = tmpEndDate
+						currVal = dataList[i].Value
+						break
+					}
+					tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					// 如果这期的日期晚于选择的日期,那么继续遍历
+					if tmpDate.After(tmpEndDate) {
+						continue
+					}
+					k = i
+					endDateStr = tmpDateStr
+					endDate = tmpDate
+					currVal = dataList[i].Value
+					break
+				}
+			}
+
+			// 没有找到日期,那么就不处理
+			if endDateStr == `` || endDate.IsZero() {
+				continue
+			}
+
+			// 最早的日期
+			earliestDate := endDate.AddDate(0, 0, -config.CalculateValue*moveUnitDays)
+			earliestDateStr := earliestDate.Format(utils.FormatDate)
+
+			var minVal, maxVal float64
+			var isNotFirst bool // 是否是第一条数据
+			for i := k; i >= 0; i-- {
+				tmpData := dataList[i]
+				if !isNotFirst {
+					maxVal = tmpData.Value
+					minVal = tmpData.Value
+					isNotFirst = true
+					continue
+				}
+
+				tmpDateStr := dataList[i].DataTime
+				// 如果正好是这一天,那么就直接break了
+				if earliestDateStr == tmpDateStr {
+					break
+				}
+				tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				// 如果这期的日期早于选择的日期,那么继续停止遍历
+				if tmpDate.Before(earliestDate) {
+					continue
+				}
+
+				if tmpData.Value > maxVal {
+					maxVal = tmpData.Value
+				}
+				if tmpData.Value < minVal {
+					minVal = tmpData.Value
+				}
+			}
+
+			// 最大值等于最小值,说明计算结果无效
+			if maxVal == minVal {
+				continue
+			}
+			//百分位=(现值-Min)/(Max-Min)
+			tmpV := (currVal - minVal) / (maxVal - minVal) * 100
+			tmpV, _ = decimal.NewFromFloat(tmpV).Round(4).Float64()
+
+			// key的生成(日期配置下标+指标id)
+			key := fmt.Sprint(dateIndex, "_", edbInfoMapping.EdbInfoId)
+			dataMap[key] = tmpV
+			dateMap[key] = endDateStr
+		}
+	}
+
+	// 返回数据处理
+	dataList := make([]SectionScatterSeriesItemResp, 0)
+	var xMinVal, xMaxVal, yMinVal, yMaxVal float64
+	var isNotFirst bool
+
+	for _, varietyId := range config.VarietyList {
+		xEdbInfoId, ok1 := xVarietyEdbMap[varietyId]
+		if !ok1 {
+			continue
+		}
+		yEdbInfoId, ok2 := yVarietyEdbMap[varietyId]
+		if !ok2 {
+			continue
+		}
+		variety, ok := varietyMap[varietyId]
+		if !ok {
+			continue
+		}
+
+		coordinatePointList := make([]CoordinatePoint, 0)
+
+		for dateIndex, _ := range config.DateConfigList {
+			key1 := fmt.Sprint(dateIndex, "_", xEdbInfoId)
+			xVal, ok1 := dataMap[key1]
+			if !ok1 {
+				continue
+			}
+			key2 := fmt.Sprint(dateIndex, "_", yEdbInfoId)
+			yVal, ok2 := dataMap[key2]
+			if !ok2 {
+				continue
+			}
+			if !isNotFirst {
+				xMinVal = xVal
+				xMaxVal = xVal
+				yMinVal = yVal
+				yMaxVal = yVal
+				isNotFirst = true
+			} else {
+				if xVal < xMinVal {
+					xMinVal = xVal
+				}
+				if xVal > xMaxVal {
+					xMaxVal = xVal
+				}
+				if yVal < yMinVal {
+					yMinVal = yVal
+				}
+				if yVal > yMaxVal {
+					yMaxVal = yVal
+				}
+			}
+			coordinatePointList = append(coordinatePointList, CoordinatePoint{
+				X:          xVal,
+				Y:          yVal,
+				XEdbInfoId: xEdbInfoId,
+				YEdbInfoId: yEdbInfoId,
+				XDate:      dateMap[key1],
+				YDate:      dateMap[key2],
+			})
+		}
+
+		dataList = append(dataList, SectionScatterSeriesItemResp{
+			Name:                variety.ChartVarietyName,
+			NameEn:              variety.ChartVarietyNameEn,
+			Color:               "",
+			CoordinatePointData: coordinatePointList,
+		})
+	}
+
+	dataResp = ChartInfoResp{
+		XName:       xTagInfo.ChartTagName + "百分位",
+		XNameEn:     xTagInfo.ChartTagNameEn,
+		XUnitName:   "%",
+		XUnitNameEn: "%",
+		YName:       yTagInfo.ChartTagName + "百分位",
+		YNameEn:     yTagInfo.ChartTagNameEn,
+		YUnitName:   "%",
+		YUnitNameEn: "%",
+		XMinValue:   fmt.Sprint(xMinVal),
+		XMaxValue:   fmt.Sprint(xMaxVal),
+		YMinValue:   fmt.Sprint(yMinVal),
+		YMaxValue:   fmt.Sprint(yMaxVal),
+		DataList:    dataList,
+	}
+
+	// 去除返回指标中的数据信息,避免没必要的数据传输
+	for k, _ := range edbList {
+		edbList[k].DataList = nil
+	}
+
+	return
+}
+
+// CheckIsEnChart
+// @Description: 检测是否展示英文名称
+// @author: Roc
+// @datetime 2023-11-28 20:54:41
+// @param chartNameEn string
+// @param dataResp ChartInfoResp
+// @return bool
+func CheckIsEnChart(chartNameEn string, dataResp ChartInfoResp) bool {
+	if chartNameEn == "" {
+		return false
+	}
+
+	if dataResp.XNameEn == `` {
+		return false
+	}
+	if dataResp.YNameEn == `` {
+		return false
+	}
+	for _, v := range dataResp.DataList {
+		if v.NameEn == `` {
+			return false
+		}
+	}
+	return true
+}
+
+// GetXYEdbIdList
+// @Description: 根据标签id和品种获取指标列表信息
+// @author: Roc
+// @datetime 2023-11-27 14:31:23
+// @param tagX int
+// @param tagY int
+// @param varietyList []int
+// @return xVarietyEdbMap map[int]int
+// @return yVarietyEdbMap map[int]int
+// @return edbInfoIdList []int
+// @return errMsg string
+// @return err error
+func GetXYEdbIdList(tagX, tagY int, varietyList []int) (xVarietyEdbMap, yVarietyEdbMap map[int]int, edbInfoIdList []int, err error) {
+	edbInfoIdList = make([]int, 0)
+	xVarietyEdbMap = make(map[int]int)
+	yVarietyEdbMap = make(map[int]int)
+	xList, err := cross_varietyModel.GetChartTagVarietyListByTagAndVariety(tagX, varietyList)
+	if err != nil {
+		err = errors.New("获取X轴的品种指标配置信息失败,Err:" + err.Error())
+		return
+	}
+	yList, err := cross_varietyModel.GetChartTagVarietyListByTagAndVariety(tagY, varietyList)
+	if err != nil {
+		err = errors.New("获取Y轴的品种指标配置信息失败,Err:" + err.Error())
+		return
+	}
+
+	baseVarietyIdMap := make(map[int]int)
+	for _, v := range xList {
+		baseVarietyIdMap[v.ChartVarietyId] = v.ChartVarietyId
+	}
+
+	// 两个标签里面的品种并集
+	needVarietyIdMap := make(map[int]int)
+	for _, v := range yList {
+		if val, ok := baseVarietyIdMap[v.ChartVarietyId]; ok {
+			// 如果在 map2 中存在相同的键,则将键和值添加到结果中
+			needVarietyIdMap[v.ChartVarietyId] = val
+		}
+	}
+
+	for _, v := range xList {
+		if _, ok := needVarietyIdMap[v.ChartVarietyId]; ok {
+			xVarietyEdbMap[v.ChartVarietyId] = v.EdbInfoId
+			edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+		}
+	}
+	for _, v := range yList {
+		if _, ok := needVarietyIdMap[v.ChartVarietyId]; ok {
+			yVarietyEdbMap[v.ChartVarietyId] = v.EdbInfoId
+			edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+		}
+	}
+
+	return
+}
+
+// AddChartInfo
+// @Description: AddChartInfo
+// @author: Roc
+// @datetime 2023-11-24 15:58:14
+// @param req request.AddChartReq
+// @param sysUser *system.Admin
+// @return chartInfo *data_manage.ChartInfo
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func AddChartInfo(req request.AddChartReq, sysUser *system.Admin) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	source := utils.CHART_SOURCE_CROSS_HEDGING
+
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartName == "" {
+		errMsg = "请填写图表名称!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.TagX <= 0 {
+		errMsg = "请选择X轴坐标的标签!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.TagY <= 0 {
+		errMsg = "请选择Y轴坐标的标签!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.CalculateValue <= 0 {
+		errMsg = "请设置时间长度!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.CalculateUnit == `` {
+		errMsg = "请设置时间频度!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 品种配置
+	if len(req.VarietyList) < 0 {
+		errMsg = "请选择品种!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 日期配置
+	dateConfigList := len(req.DateConfigList)
+	if dateConfigList < 0 {
+		errMsg = "请选择日期!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if dateConfigList > 5 {
+		errMsg = "日期数量已达上限!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	// 基础配置转string
+	extraConfigByte, err := json.Marshal(req)
+	if err != nil {
+		return
+	}
+
+	chartClassify, err := data_manage.GetCrossVarietyChartClassifyBySysUserId(sysUser.AdminId)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			errMsg = "获取分类信息失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+
+		// 分类没有,需要创建新的分类,那么err置空
+		err = nil
+
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		chartClassify = &data_manage.ChartClassify{
+			ChartClassifyId:   0,
+			ChartClassifyName: sysUser.RealName,
+			ParentId:          0,
+			HasData:           0,
+			CreateTime:        time.Now(),
+			ModifyTime:        time.Now(),
+			SysUserId:         sysUser.AdminId,
+			SysUserRealName:   sysUser.RealName,
+			Level:             1,
+			UniqueCode:        utils.MD5(utils.DATA_PREFIX + "_" + timestamp),
+			Sort:              0,
+			Source:            source,
+		}
+		_, err = data_manage.AddChartClassify(chartClassify)
+	}
+
+	var chartInfoId int
+	// 判断图表是否存在
+	//var condition string
+	//var pars []interface{}
+	//condition += " AND chart_name=? AND source = ? "
+	//pars = append(pars, req.ChartName, source)
+	//count, err := data_manage.GetChartInfoCountByCondition(condition, pars)
+	//if err != nil {
+	//	errMsg = "判断图表名称是否存在失败"
+	//	err = errors.New("判断图表名称是否存在失败,Err:" + err.Error())
+	//	return
+	//}
+
+	//if count > 0 {
+	//	errMsg = "图表已存在,请重新填写"
+	//	err = errors.New(errMsg)
+	//	isSendEmail = false
+	//	return
+	//}
+
+	chartInfo = new(data_manage.ChartInfo)
+	chartInfo.ChartName = req.ChartName
+	//chartInfo.EdbInfoIds = edbInfoIdStr
+	//chartInfo.ChartClassifyId = req.ChartClassifyId
+	chartInfo.SysUserId = sysUser.AdminId
+	chartInfo.SysUserRealName = sysUser.RealName
+	chartInfo.ChartImage = req.ChartImage
+	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 = 9 // 相关性图
+	chartInfo.Calendar = "公历"
+	chartInfo.DateType = 6
+	//chartInfo.StartDate = req.StartDate
+	//chartInfo.EndDate = req.EndDate
+	//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.ExtraConfig = string(extraConfigByte)
+
+	// 图表品种
+	chartVarietyMappingList := make([]*cross_varietyModel.ChartVarietyMapping, 0)
+	for _, varietyId := range req.VarietyList {
+		chartVarietyMappingList = append(chartVarietyMappingList, &cross_varietyModel.ChartVarietyMapping{
+			Id:             0,
+			ChartInfoId:    0,
+			ChartVarietyId: varietyId,
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		})
+	}
+
+	// 图表配置
+	chartInfoCrossVariety := &cross_varietyModel.ChartInfoCrossVariety{
+		Id:             0,
+		ChartInfoId:    0,
+		ChartXTagId:    req.TagX,
+		ChartYTagId:    req.TagY,
+		CalculateValue: req.CalculateValue,
+		CalculateUnit:  req.CalculateUnit,
+		ModifyTime:     time.Now(),
+		CreateTime:     time.Now(),
+	}
+
+	// 新增图表和指标mapping
+	chartInfoId, e := cross_varietyModel.CreateChart(chartInfo, chartClassify, chartVarietyMappingList, chartInfoCrossVariety)
+	if e != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增相关性图表失败, Err: " + e.Error())
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartInfoId)
+
+	return
+}
+
+// EditChartInfo
+// @Description: 编辑图表
+// @author: Roc
+// @datetime 2023-11-24 15:58:31
+// @param req request.EditChartReq
+// @param sysUser *system.Admin
+// @return chartItem *data_manage.ChartInfo
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func EditChartInfo(req request.EditChartReq, sysUser *system.Admin) (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_CROSS_HEDGING {
+		errMsg = "该图不是跨品种分析图表!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartName == "" {
+		errMsg = "请填写图表名称!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.TagX <= 0 {
+		errMsg = "请选择X轴坐标的标签!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.TagY <= 0 {
+		errMsg = "请选择Y轴坐标的标签!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.CalculateValue <= 0 {
+		errMsg = "请设置时间长度!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.CalculateUnit == `` {
+		errMsg = "请设置时间频度!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 品种配置
+	if len(req.VarietyList) < 0 {
+		errMsg = "请选择品种!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 日期配置
+	dateConfigList := len(req.DateConfigList)
+	if dateConfigList < 0 {
+		errMsg = "请选择日期!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if dateConfigList > 5 {
+		errMsg = "日期数量已达上限!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 基础配置转string
+	extraConfigByte, err := json.Marshal(req)
+	if err != nil {
+		return
+	}
+
+	// 图表操作权限
+	ok := data.CheckOpChartPermission(sysUser, chartItem.SysUserId)
+	if !ok {
+		errMsg = "没有该图表的操作权限"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	//判断图表是否存在
+	//var condition string
+	//var pars []interface{}
+	//condition += " AND chart_info_id <> ? "
+	//pars = append(pars, req.ChartInfoId)
+	//condition += " AND chart_name=? AND source = ? "
+	//pars = append(pars, req.ChartName, chartItem.Source)
+	//count, err := data_manage.GetChartInfoCountByCondition(condition, pars)
+	//if err != nil {
+	//	errMsg = "判断图表名称是否存在失败"
+	//	err = errors.New("判断图表名称是否存在失败,Err:" + err.Error())
+	//	return
+	//}
+	//if count > 0 {
+	//	errMsg = "图表已存在,请重新填写"
+	//	err = errors.New(errMsg)
+	//	isSendEmail = false
+	//	return
+	//}
+
+	chartItem.ChartName = req.ChartName
+	chartItem.ExtraConfig = string(extraConfigByte)
+	chartItem.ModifyTime = time.Now()
+	chartUpdateCols := []string{"ChartName", "ExtraConfig", "ModifyTime"}
+
+	// 跨品种分析配置
+	chartInfoCrossVariety, err := cross_varietyModel.GetChartInfoCrossVarietyByChartInfoId(chartItem.ChartInfoId)
+	if err != nil {
+		return
+	}
+	chartInfoCrossVariety.ChartXTagId = req.TagX
+	chartInfoCrossVariety.ChartYTagId = req.TagY
+	chartInfoCrossVariety.CalculateValue = req.CalculateValue
+	chartInfoCrossVariety.CalculateUnit = req.CalculateUnit
+	chartInfoCrossVariety.ModifyTime = time.Now()
+	chartInfoCrossVarietyUpdateCols := []string{"ChartXTagId", "ChartYTagId", "CalculateValue", "CalculateUnit", "ModifyTime"}
+
+	// 图表品种
+	chartVarietyMappingList := make([]*cross_varietyModel.ChartVarietyMapping, 0)
+	for _, varietyId := range req.VarietyList {
+		chartVarietyMappingList = append(chartVarietyMappingList, &cross_varietyModel.ChartVarietyMapping{
+			Id:             0,
+			ChartInfoId:    0,
+			ChartVarietyId: varietyId,
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		})
+	}
+
+	err = cross_varietyModel.EditChart(chartItem, chartVarietyMappingList, chartInfoCrossVariety,
+		chartUpdateCols, chartInfoCrossVarietyUpdateCols)
+	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
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	return
+}
+
+// ModifyChartEdbMapping
+// @Description: 修改图表的关系表
+// @author: Roc
+// @datetime 2023-12-11 17:32:31
+// @param tagId int
+func ModifyChartEdbMapping(tagId int) {
+	errMsgList := make([]string, 0)
+	var err error
+	defer func() {
+		if err != nil {
+			errMsgList = append(errMsgList, err.Error())
+		}
+		if len(errMsgList) > 0 {
+			utils.FileLog.Error("修改跨品种分析的图表关联指标失败:\n" + strings.Join(errMsgList, "\n"))
+		}
+	}()
+	// 找出标签关联的图表
+	list, err := cross_varietyModel.GeChartInfoCrossVarietyListByTagId(tagId)
+	if err != nil {
+		return
+	}
+
+	chartInfoIdList := make([]int, 0)
+
+	tagIdList := make([]int, 0)
+	tagIdMap := make(map[int]int, 0)
+	for _, v := range list {
+		chartInfoIdList = append(chartInfoIdList, v.ChartInfoId)
+
+		if _, ok := tagIdMap[v.ChartXTagId]; !ok {
+			tagIdList = append(tagIdList, v.ChartXTagId)
+			tagIdMap[v.ChartXTagId] = 1
+		}
+
+		if _, ok := tagIdMap[v.ChartYTagId]; !ok {
+			tagIdList = append(tagIdList, v.ChartYTagId)
+			tagIdMap[v.ChartYTagId] = 1
+		}
+	}
+
+	// 找出所有标签关联的品种和对应的指标
+	chartTagVarietyList, err := cross_varietyModel.GetChartTagVarietyListByTagIdList(tagIdList)
+	if err != nil {
+		return
+	}
+
+	// 所有指标id
+	allEdbInfoIdList := make([]int, 0)
+
+	// 标签关联品种的指标列表
+	tagVarietyEdbMap := make(map[string][]int)
+	for _, v := range chartTagVarietyList {
+		if v.EdbInfoId <= 0 {
+			continue
+		}
+		key := fmt.Sprint(v.ChartTagId, "_", v.ChartVarietyId)
+		tagVarietyEdbList, ok := tagVarietyEdbMap[key]
+		if !ok {
+			tagVarietyEdbList = make([]int, 0)
+		}
+		tagVarietyEdbMap[key] = append(tagVarietyEdbList, v.EdbInfoId)
+		allEdbInfoIdList = append(allEdbInfoIdList, v.EdbInfoId)
+	}
+
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	// 获取指标列表
+	if len(allEdbInfoIdList) > 0 {
+		allEdbInfoList, tmpErr := data_manage.GetEdbInfoByIdList(allEdbInfoIdList)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		for _, v := range allEdbInfoList {
+			edbInfoMap[v.EdbInfoId] = v
+		}
+	}
+
+	// 图表关联的品种列表
+	chartVarietyMappingList, err := cross_varietyModel.GetChartVarietyMappingListByChartInfoIdList(chartInfoIdList)
+	if err != nil {
+		return
+	}
+
+	// 图表关联的品种map
+	chartVarietyMappingListMap := make(map[int][]int)
+	for _, v := range chartVarietyMappingList {
+		if v.ChartInfoId <= 0 {
+			continue
+		}
+		varietyMappingList, ok := chartVarietyMappingListMap[v.ChartInfoId]
+		if !ok {
+			varietyMappingList = make([]int, 0)
+		}
+		chartVarietyMappingListMap[v.ChartInfoId] = append(varietyMappingList, v.ChartVarietyId)
+	}
+
+	// 处理图表
+	for _, v := range list {
+		// 获取关联的指标id
+		edbInfoList := make([]*data_manage.EdbInfo, 0)
+		edbInfoIdMap := make(map[int]int, 0)
+
+		// 找出图表关联的品种
+		varietyIdList, ok := chartVarietyMappingListMap[v.ChartInfoId]
+		if ok {
+			// 获取品种与标签的id
+			for _, varietyId := range varietyIdList {
+				// 先处理x轴
+				key := fmt.Sprint(v.ChartXTagId, "_", varietyId)
+				if tmpEdbInfoIdList, ok2 := tagVarietyEdbMap[key]; ok2 {
+					for _, edbInfoId := range tmpEdbInfoIdList {
+						if _, ok3 := edbInfoIdMap[edbInfoId]; !ok3 {
+							if edbInfo, ok4 := edbInfoMap[edbInfoId]; ok4 {
+								edbInfoList = append(edbInfoList, edbInfo)
+							}
+							edbInfoIdMap[edbInfoId] = edbInfoId
+						}
+					}
+				}
+
+				// 处理y轴
+				key = fmt.Sprint(v.ChartYTagId, "_", varietyId)
+				if tmpEdbInfoIdList, ok2 := tagVarietyEdbMap[key]; ok2 {
+					for _, edbInfoId := range tmpEdbInfoIdList {
+						if _, ok3 := edbInfoIdMap[edbInfoId]; !ok3 {
+							if edbInfo, ok4 := edbInfoMap[edbInfoId]; ok4 {
+								edbInfoList = append(edbInfoList, edbInfo)
+							}
+							edbInfoIdMap[edbInfoId] = edbInfoId
+						}
+					}
+				}
+			}
+		}
+
+		tmpErr := data_manage.ModifyChartEdbMapping(v.ChartInfoId, edbInfoList)
+		if tmpErr != nil {
+			errMsgList = append(errMsgList, fmt.Sprint("修改", v.ChartInfoId, "失败,err:", tmpErr))
+		}
+	}
+
+	return
+}

+ 80 - 1
services/data/edb_classify.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/cross_variety"
 	"eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services/alarm_msg"
@@ -430,6 +431,23 @@ func DeleteCheck(classifyId, edbInfoId int, sysUser *system.Admin) (deleteStatus
 
 	//删除指标
 	if edbInfoId > 0 {
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(edbInfoId)
+		if tmpErr != nil {
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "指标已删除,请刷新页面"
+				err = errors.New("指标不存在,Err:" + tmpErr.Error())
+				return
+			} else {
+				errMsg = "删除失败"
+				err = errors.New("删除失败,获取指标信息失败,Err:" + tmpErr.Error())
+				return
+			}
+		}
+		if edbInfo == nil {
+			errMsg = "指标已删除,请刷新页面"
+			return
+		}
+
 		//判断指标是否用于作图,如果用于作图,则不可删除
 		chartCount, tmpErr := data_manage.GetChartEdbMappingCount(edbInfoId)
 		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
@@ -440,6 +458,7 @@ func DeleteCheck(classifyId, edbInfoId int, sysUser *system.Admin) (deleteStatus
 		if chartCount > 0 {
 			deleteStatus = 3
 			tipsMsg = "当前指标已用作画图,不可删除"
+			return
 		}
 		//判断指标是否用于计算
 		{
@@ -452,6 +471,52 @@ func DeleteCheck(classifyId, edbInfoId int, sysUser *system.Admin) (deleteStatus
 			if calculateCount > 0 {
 				deleteStatus = 4
 				tipsMsg = "当前指标已用作,指标运算,不可删除"
+				return
+			}
+		}
+
+		//如果是普通指标,那么还需要判断是否被预测指标作为源指标
+		if edbInfo.EdbInfoType == 0 {
+			predictEdbInfoCount, tmpErr := data_manage.GetPredictEdbConfCount(edbInfoId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "删除失败"
+				err = errors.New("判断指标是否被用于预测指标失败,Err:" + tmpErr.Error())
+				return
+			}
+			if predictEdbInfoCount > 0 {
+				deleteStatus = 3
+				tipsMsg = "当前指标已用作预测指标,不可删除"
+				return
+			}
+		}
+
+		// 判断指标是否用作表格引用
+		{
+			calculateCount, tmpErr := excel.GetNoCustomAnalysisExcelEdbMappingCount(edbInfoId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "删除失败"
+				err = errors.New("判断指标是否用作表格引用,GetNoCustomAnalysisExcelEdbMappingCount Err:" + tmpErr.Error())
+				return
+			}
+			if calculateCount > 0 {
+				deleteStatus = 3
+				tipsMsg = "当前指标已添加到表格,不可删除"
+				return
+			}
+		}
+
+		// 判断指标是否用作跨品种图表使用
+		{
+			calculateCount, tmpErr := cross_variety.GetCountByEdbInfoId(edbInfoId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "删除失败"
+				err = errors.New("判断指标是否用作跨品种图表使用,GetCountByEdbInfoId Err:" + tmpErr.Error())
+				return
+			}
+			if calculateCount > 0 {
+				deleteStatus = 3
+				tipsMsg = "当前指标已添加到跨品种分析,不可删除"
+				return
 			}
 		}
 	}
@@ -574,7 +639,7 @@ func Delete(classifyId, edbInfoId int, sysUser *system.Admin, requestBody, reque
 			calculateCount, tmpErr := excel.GetNoCustomAnalysisExcelEdbMappingCount(edbInfoId)
 			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
 				errMsg = "删除失败"
-				err = errors.New("当前指标已添加到表格,GetNoCustomAnalysisExcelEdbMappingCount Err:" + tmpErr.Error())
+				err = errors.New("判断指标是否用作表格引用,GetNoCustomAnalysisExcelEdbMappingCount Err:" + tmpErr.Error())
 				return
 			}
 			if calculateCount > 0 {
@@ -583,6 +648,20 @@ func Delete(classifyId, edbInfoId int, sysUser *system.Admin, requestBody, reque
 			}
 		}
 
+		// 判断指标是否用作跨品种图表使用
+		{
+			calculateCount, tmpErr := cross_variety.GetCountByEdbInfoId(edbInfoId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "删除失败"
+				err = errors.New("判断指标是否用作跨品种图表使用,GetCountByEdbInfoId Err:" + tmpErr.Error())
+				return
+			}
+			if calculateCount > 0 {
+				errMsg = "当前指标已添加到跨品种分析,不可删除"
+				return
+			}
+		}
+
 		//真实删除
 		tmpErr = data_manage.DeleteEdbInfoAndData(edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource)
 		if tmpErr != nil {

+ 80 - 8
services/data/edb_info.go

@@ -17,7 +17,7 @@ import (
 )
 
 // EdbInfoRefreshAllFromBaseV2 全部刷新指标(切换到edb_lib服务)
-func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll bool) (err error, isAsync bool) {
+func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll, isRefreshTop bool) (err error, isAsync bool) {
 	var errmsg string
 	defer func() {
 		if err != nil {
@@ -27,7 +27,7 @@ func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll bool) (err error, isA
 		}
 	}()
 
-	err, isAsync = EdbInfoRefreshAllFromBaseV3([]int{edbInfoId}, refreshAll, false)
+	err, isAsync = EdbInfoRefreshAllFromBaseV3([]int{edbInfoId}, refreshAll, false, isRefreshTop)
 	return
 }
 
@@ -70,13 +70,14 @@ func EdbInfoRefreshAllFromBaseV3Bak(edbInfoIdList []int, refreshAll, isSync bool
 //
 //	@Description: 全部刷新指标(切换到edb_lib服务)
 //	@author: Roc
-//	@datetime2023-10-23 09:57:55
+//	@datetime 2023-10-23 09:57:55
 //	@param edbInfoIdList []int
 //	@param refreshAll bool
 //	@param isSync bool 是否同步执行
+//	@param isRefreshTop bool 是否刷新上面的指标
 //	@return err error
 //	@return isAsync bool 是否异步刷新
-func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (err error, isAsync bool) {
+func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync, isRefreshTop bool) (err error, isAsync bool) {
 	var errmsg string
 	defer func() {
 		if err != nil {
@@ -85,8 +86,21 @@ func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (
 		}
 	}()
 
-	// 获取需要刷新的指标列表
-	newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr := getEdbInfoIdList(edbInfoIdList)
+	var newBaseEdbInfoArr, newBasePredictEdbInfoArr []*data_manage.EdbInfo
+	var newCalculateMap, newPredictCalculateMap map[int]*data_manage.EdbInfo
+	var calculateArr, predictCalculateArr []int
+
+	// 获取关联指标
+	if isRefreshTop {
+		// 获取所有关联的指标,上下所有的指标
+		newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr, err, errmsg = getRefreshEdbInfoListByIds(edbInfoIdList)
+		if err != nil {
+			return
+		}
+	} else {
+		// 获取溯源的指标
+		newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr = getEdbInfoIdList(edbInfoIdList)
+	}
 
 	// 需要刷新的指标数量
 	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
@@ -114,6 +128,7 @@ func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (
 // @return predictCalculateArr []int
 func getEdbInfoIdList(edbInfoIdList []int) (newBaseEdbInfoArr, newBasePredictEdbInfoArr []*data_manage.EdbInfo, newCalculateMap, newPredictCalculateMap map[int]*data_manage.EdbInfo, calculateArr, predictCalculateArr []int) {
 	traceEdbInfoList, err := TraceEdbInfoByEdbInfoIdList(edbInfoIdList)
+
 	if err != nil {
 		return
 	}
@@ -1182,7 +1197,7 @@ func RefreshManualData(edbCode string) {
 		return
 	}
 	// 刷新指标库
-	err, _ = EdbInfoRefreshAllFromBaseV2(edbInfo.EdbInfoId, true)
+	err, _ = EdbInfoRefreshAllFromBaseV2(edbInfo.EdbInfoId, true, true)
 	return
 }
 
@@ -2841,7 +2856,7 @@ func EdbInfoReplace(oldEdbInfo, newEdbInfo *data_manage.EdbInfo, sysAdminId int,
 	}
 
 	// 更新所有的关联指标
-	err, _ = EdbInfoRefreshAllFromBaseV3(relationEdbInfoIdList, true, true)
+	err, _ = EdbInfoRefreshAllFromBaseV3(relationEdbInfoIdList, true, true, true)
 	return
 }
 
@@ -3042,3 +3057,60 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	AddOrEditEdbInfoToEs(int(edbInfoId))
 	return
 }
+
+// BatchRefreshEdbByEdbIds 批量刷新指标
+func BatchRefreshEdbByEdbIds(edbIdList []int, redisKey string, refreshKeys []string) (syncing bool, err error) {
+	if len(edbIdList) <= 0 {
+		return
+	}
+
+	// 设置刷新缓存
+	if redisKey != `` {
+		// 设置最多10分钟缓存
+		utils.Rc.SetNX(redisKey, 1, time.Minute*10)
+	}
+
+	// 获取需要刷新的指标列表
+	newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr := getEdbInfoIdList(edbIdList)
+
+	// 需要刷新的指标数量
+	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
+
+	// 关联指标过多的时候, 异步刷新
+	if totalEdbInfo > 20 {
+		syncing = true
+
+		go func() {
+			defer func() {
+				if err != nil {
+					alarm_msg.SendAlarmMsg("BatchRefreshEdbByEdbIds, ErrMsg: "+err.Error(), 3)
+				}
+			}()
+
+			err = edbInfoRefreshAll(false, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
+
+			if redisKey != `` {
+				_ = utils.Rc.Delete(redisKey)
+			}
+			if len(refreshKeys) > 0 {
+				for _, v := range refreshKeys {
+					_ = utils.Rc.Delete(v)
+				}
+			}
+		}()
+		return
+	}
+
+	// 同步刷新
+	err = edbInfoRefreshAll(false, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
+
+	if redisKey != `` {
+		_ = utils.Rc.Delete(redisKey)
+	}
+	if len(refreshKeys) > 0 {
+		for _, v := range refreshKeys {
+			_ = utils.Rc.Delete(v)
+		}
+	}
+	return
+}

+ 153 - 102
services/data/edb_info_calculate.go

@@ -9,13 +9,14 @@ import (
 	"github.com/shopspring/decimal"
 	"github.com/yidane/formula"
 	"math"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
 )
 
 func CheckFormula(formula string) map[string]string {
-	mathFormula := []string{"MAX", "MIN", "ABS", "ACOS", "ASIN", "CEIL", "MOD", "POW", "ROUND", "SIGN", "SIN", "TAN", "LOG10", "LOG2", "LOG", "LN"}
+	mathFormula := []string{"MAX", "MIN", "ABS", "ACOS", "ASIN", "CEIL", "MOD", "POW", "ROUND", "SIGN", "SIN", "TAN", "LOG10", "LOG2", "LOG", "LN", "EXP"}
 
 	str := strings.ToUpper(formula)
 	for _, v := range mathFormula {
@@ -37,22 +38,23 @@ func CheckFormula(formula string) map[string]string {
 	return byteMap
 }
 
-// CheckFormula2 校验公式是否正常(比如说除法的分母不能为0之类的,实际上就是用预设的字段数据做一次计算)
-func CheckFormula2(edbInfoArr []*data_manage.EdbInfo, formulaMap map[string]string, formulaStr string, edbInfoIdBytes []string) (ok bool, err error) {
-	valArr := make(map[int]float64)
-	for _, v := range edbInfoArr {
-		valArr[v.EdbInfoId] = 100
-	}
-	formulaStr = strings.ToUpper(formulaStr)
-	formulaFormStr := ReplaceFormula(edbInfoArr, valArr, formulaMap, formulaStr, edbInfoIdBytes)
-	if formulaFormStr == "" {
+type FormulaListItem struct {
+	Formula string `json:"f"`
+	Date    string `json:"d"`
+}
+
+// CheckFormulaJson 检测计算公式json串是否异常
+func CheckFormulaJson(formula string) (formulaSlice []string, err error) {
+	list := make([]FormulaListItem, 0)
+	err = json.Unmarshal([]byte(formula), &list)
+	if err != nil {
+		err = fmt.Errorf("公式串解析失败: json.Unmarshal Err: %v", err)
 		return
 	}
-	expression := formula.NewExpression(formulaFormStr)
-	_, err = expression.Evaluate()
-	if err != nil {
-	} else {
-		ok = true
+	formulaSlice = make([]string, 0)
+	// 日期排序
+	for _, v := range list {
+		formulaSlice = append(formulaSlice, v.Formula)
 	}
 	return
 }
@@ -62,91 +64,8 @@ type CalculateItems struct {
 	DataMap   map[string]float64
 }
 
-func Calculate(edbInfoIdArr []*data_manage.EdbInfo, edbInfoId int, edbCode, formulaStr string, edbInfoIdBytes []string) (err error) {
-	defer func() {
-		if err != nil {
-			utils.FileLog.Info("Calculate Err:%s" + err.Error())
-		}
-	}()
-	saveDataMap := make(map[string]map[int]float64)
-	for _, v := range edbInfoIdArr {
-		var condition string
-		var pars []interface{}
-		condition += " AND edb_info_id=? "
-		pars = append(pars, v.EdbInfoId)
-		dataList, err := data_manage.GetEdbDataListAll(condition, pars, v.Source, v.SubSource, 1)
-		if err != nil {
-			return err
-		}
-		dataMap := make(map[string]float64)
-		for _, dv := range dataList {
-			if val, ok := saveDataMap[dv.DataTime]; ok {
-				if _, ok := val[v.EdbInfoId]; !ok {
-					val[v.EdbInfoId] = dv.Value
-				}
-			} else {
-				temp := make(map[int]float64)
-				temp[v.EdbInfoId] = dv.Value
-				saveDataMap[dv.DataTime] = temp
-			}
-		}
-		item := new(CalculateItems)
-		item.EdbInfoId = v.EdbInfoId
-		item.DataMap = dataMap
-	}
-	formulaMap := CheckFormula(formulaStr)
-	addSql := ` INSERT INTO edb_data_calculate(edb_info_id,edb_code,data_time,value,create_time,modify_time,status,data_timestamp) values `
-	nowStr := time.Now().Format(utils.FormatDateTime)
-	var isAdd bool
-	for sk, sv := range saveDataMap {
-		formulaStr = strings.ToUpper(formulaStr)
-		formulaFormStr := ReplaceFormula(edbInfoIdArr, sv, formulaMap, formulaStr, edbInfoIdBytes)
-		if formulaStr == "" {
-			return
-		}
-		if formulaFormStr != "" {
-			expression := formula.NewExpression(formulaFormStr)
-			calResult, err := expression.Evaluate()
-			if err != nil {
-				err = errors.New("计算失败:Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
-				fmt.Println(err)
-				return err
-			}
-			calVal, err := calResult.Float64()
-			if err != nil {
-				err = errors.New("计算失败:获取计算值失败 Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
-				fmt.Println(err)
-				return err
-			}
-
-			//需要存入的数据
-			{
-				dataTime, _ := time.Parse(utils.FormatDate, sk)
-				timestamp := dataTime.UnixNano() / 1e6
-				timeStr := fmt.Sprintf("%d", timestamp)
-				addSql += "("
-				addSql += strconv.Itoa(edbInfoId) + "," + "'" + edbCode + "'" + "," + "'" + sk + "'" + "," + utils.SubFloatToString(calVal, 4) + "," + "'" + nowStr + "'" +
-					"," + "'" + nowStr + "'" + "," + "1"
-				addSql += "," + "'" + timeStr + "'"
-				addSql += "),"
-				isAdd = true
-			}
-		} else {
-			fmt.Println("formulaFormStr is empty")
-		}
-	}
-	if isAdd {
-		addSql = strings.TrimRight(addSql, ",")
-		data_manage.AddEdbDataCalculateBySql(addSql)
-		if err != nil {
-			fmt.Println("AddEdbDataCalculate Err:" + err.Error())
-			return err
-		}
-	}
-	return
-}
-
 func ReplaceFormula(edbInfoIdArr []*data_manage.EdbInfo, valArr map[int]float64, formulaMap map[string]string, formulaStr string, edbInfoIdBytes []string) string {
+	// todo 处理min和max
 	funMap := GetFormulaMap()
 	for k, v := range funMap {
 		formulaStr = strings.Replace(formulaStr, k, v, -1)
@@ -548,7 +467,22 @@ func RefreshCalculate(edbInfoIdArr []*data_manage.EdbInfo, edbInfoId int, edbCod
 }
 
 // 处理整个数据
-func handleDateSaveDataMap(dateList []string, realSaveDataMap, saveDataMap map[string]map[int]float64, edbInfoIdArr []*data_manage.EdbInfo) {
+func handleDateSaveDataMap(dateList []string, realSaveDataMap, saveDataMap map[string]map[int]float64, edbInfoIdArr []*data_manage.EdbInfo, emptyType int) {
+	var startDate, endDate string
+	var startDateT, endDateT time.Time
+	if emptyType == 2 || emptyType == 3 {
+		for k, _ := range realSaveDataMap {
+			if k > endDate {
+				endDate = k
+			}
+			if k < startDate || startDate == "" {
+				startDate = k
+			}
+		}
+
+		startDateT, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		endDateT, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+	}
 	for _, date := range dateList {
 		tmpDataMap := realSaveDataMap[date]
 		for _, edbInfo := range edbInfoIdArr {
@@ -569,8 +503,16 @@ func handleDateSaveDataMap(dateList []string, realSaveDataMap, saveDataMap map[s
 				//	day = 365
 				//}
 				// 需求池 255 指标运算文案修改,补数据遍历区间修改(2023-3-7 09:37:23修改)
-				day := 35
-				handleDateDataMap(realSaveDataMap, saveDataMap, date, tmpEdbInfoId, day)
+				switch emptyType {
+				case 0:
+					handleDateDataMap(realSaveDataMap, saveDataMap, date, tmpEdbInfoId, 35)
+				case 2:
+					handleDateDataMapBefore(realSaveDataMap, saveDataMap, date, tmpEdbInfoId, startDateT, endDateT)
+				case 3:
+					handleDateDataMapAfter(realSaveDataMap, saveDataMap, date, tmpEdbInfoId, startDateT, endDateT)
+				case 4:
+					handleDateDataMapZero(saveDataMap, date, tmpEdbInfoId)
+				}
 			}
 		}
 	}
@@ -711,3 +653,112 @@ func CallCalculateComputeCorrelation(data *data_manage.EdbInfoCalculateBatchSave
 
 	return
 }
+
+// handleDateDataMapBefore 前值填充:空值优先以最近的前值填充,没有前值时,用后值填充
+func handleDateDataMapBefore(realSaveDataMap, saveDataMap map[string]map[int]float64, date string, edbInfoId int, startDateT, endDateT time.Time) {
+	currDate, _ := time.ParseInLocation(utils.FormatDate, date, time.Local)
+
+	// 后一天
+	nextDateDay := currDate
+
+	// 前一天
+	preDateDay := currDate
+
+	for i := 1; preDateDay.After(startDateT) || preDateDay == startDateT; i++ {
+		// 上个日期的数据
+		{
+			preDateDay = currDate.AddDate(0, 0, -i)
+			preDateDayStr := preDateDay.Format(utils.FormatDate)
+			if findDataMap, hasFindDataMap := realSaveDataMap[preDateDayStr]; hasFindDataMap { // 下一个日期有数据
+				if val, hasFindItem := findDataMap[edbInfoId]; hasFindItem {
+					fmt.Println(fmt.Sprintf("date:%s, 无值,取%s的值%.4f", date, preDateDayStr, val))
+					saveDataMap[date][edbInfoId] = val
+					return
+				}
+			}
+		}
+	}
+
+	for i := 1; nextDateDay.Before(endDateT) || nextDateDay == endDateT; i++ {
+		// 下个日期的数据
+		{
+			nextDateDay = currDate.AddDate(0, 0, i)
+			nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+			if findDataMap, hasFindDataMap := realSaveDataMap[nextDateDayStr]; hasFindDataMap { // 下一个日期有数据
+				if val, hasFindItem := findDataMap[edbInfoId]; hasFindItem {
+					fmt.Println(fmt.Sprintf("date:%s, 无值,取%s的值%.4f", date, nextDateDayStr, val))
+					saveDataMap[date][edbInfoId] = val
+					return
+				}
+			}
+		}
+	}
+	return
+}
+
+// handleDateDataMapAfter 后值填充:空值优先以最近的后值填充,没有后值时,用前值填充
+func handleDateDataMapAfter(realSaveDataMap, saveDataMap map[string]map[int]float64, date string, edbInfoId int, startDateT, endDateT time.Time) {
+	currDate, _ := time.ParseInLocation(utils.FormatDate, date, time.Local)
+
+	// 后一天
+	nextDateDay := currDate
+
+	// 前一天
+	preDateDay := currDate
+
+	for i := 1; nextDateDay.Before(endDateT) || nextDateDay == endDateT; i++ {
+		// 下个日期的数据
+		{
+			nextDateDay = currDate.AddDate(0, 0, i)
+			nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+			if findDataMap, hasFindDataMap := realSaveDataMap[nextDateDayStr]; hasFindDataMap { // 下一个日期有数据
+				if val, hasFindItem := findDataMap[edbInfoId]; hasFindItem {
+					fmt.Println(fmt.Sprintf("date:%s, 无值,取%s的值%.4f", date, nextDateDayStr, val))
+					saveDataMap[date][edbInfoId] = val
+					return
+				}
+			}
+		}
+	}
+
+	for i := 1; preDateDay.After(startDateT) || preDateDay == startDateT; i++ {
+		// 上个日期的数据
+		{
+			preDateDay = currDate.AddDate(0, 0, -i)
+			preDateDayStr := preDateDay.Format(utils.FormatDate)
+			if findDataMap, hasFindDataMap := realSaveDataMap[preDateDayStr]; hasFindDataMap { // 下一个日期有数据
+				if val, hasFindItem := findDataMap[edbInfoId]; hasFindItem {
+					fmt.Println(fmt.Sprintf("date:%s, 无值,取%s的值%.4f", date, preDateDayStr, val))
+					saveDataMap[date][edbInfoId] = val
+					return
+				}
+			}
+		}
+	}
+	return
+}
+
+// handleDateDataMapZero 等于0
+func handleDateDataMapZero(saveDataMap map[string]map[int]float64, date string, edbInfoId int) {
+	saveDataMap[date][edbInfoId] = 0
+	return
+}
+
+func GetMaxMinEdbInfo(formula string) string {
+	//formula := "A+min(A,B,max(A,C))"
+	// todo 无法处理max里嵌套max或者min的情况
+	// 使用正则表达式匹配MAX和MIN函数及其参数
+	regex := regexp.MustCompile(`(?i)(MAX|MIN)\((.*?)\)`)
+	matches := regex.FindAllStringSubmatch(formula, -1)
+	// 遍历匹配结果,输出MAX和MIN函数及其参数
+	for _, match := range matches {
+		if len(match) == 3 {
+			parameter := strings.ToLower(match[0]) // 参数
+			formula = strings.ReplaceAll(formula, match[0], parameter)
+			fmt.Printf("formula: %s\n", formula)
+		}
+	}
+	formula = strings.ReplaceAll(formula, "max", "MAX")
+	formula = strings.ReplaceAll(formula, "min", "MIN")
+	return formula
+}

+ 8 - 0
services/data/excel/custom_analysis_edb.go

@@ -250,7 +250,11 @@ func Refresh(excelInfo *excel.ExcelInfo) (err error, errMsg string, isSendEmail
 		DataList  []float64
 	}
 
+	edbInfoIdList := make([]int, 0)
+
 	for _, v := range list {
+		edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+
 		dateList := make([]string, 0)
 		dataList := make([]string, 0)
 
@@ -452,6 +456,10 @@ func Refresh(excelInfo *excel.ExcelInfo) (err error, errMsg string, isSendEmail
 		//sheetInfo.Cell()
 	}
 
+	if len(edbInfoIdList) > 0 {
+		err, _ = data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, true, true)
+	}
+
 	//xlsxFile.Sheet[]
 
 	return

+ 91 - 1
services/data/excel/excel_info.go

@@ -33,6 +33,27 @@ func GetExcelDetailInfoByExcelInfoId(excelInfoId int) (excelDetail response.Exce
 		return
 	}
 
+	return formatExcelInfo2Detail(excelInfo)
+}
+
+// GetExcelDetailInfoByUnicode 根据表格编码获取表格详情
+func GetExcelDetailInfoByUnicode(unicode string) (excelDetail response.ExcelInfoDetail, errMsg string, err error) {
+	errMsg = `获取失败`
+	// 获取eta表格信息
+	excelInfo, err := excel.GetExcelInfoByUnicode(unicode)
+	if err != nil {
+		err = errors.New("获取ETA表格信息失败,Err:" + err.Error())
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "ETA表格被删除,请刷新页面"
+			err = errors.New("ETA表格被删除,请刷新页面,Err:" + err.Error())
+		}
+		return
+	}
+
+	return formatExcelInfo2Detail(excelInfo)
+}
+
+func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo) (excelDetail response.ExcelInfoDetail, errMsg string, err error) {
 	excelDetail = response.ExcelInfoDetail{
 		ExcelInfoId:     excelInfo.ExcelInfoId,
 		Source:          excelInfo.Source,
@@ -85,7 +106,6 @@ func GetExcelDetailInfoByExcelInfoId(excelInfoId int) (excelDetail response.Exce
 		result.Data = newData
 		excelDetail.TableData = result
 	}
-
 	return
 }
 
@@ -1262,3 +1282,73 @@ func calculate(calculateFormula string, TagMap map[string]float64) (calVal, errM
 
 	return
 }
+
+// GetEdbIdsFromExcelCodes 获取表格中的指标IDs
+func GetEdbIdsFromExcelCodes(excelCodes []string) (edbIds []int, err error) {
+	edbIds = make([]int, 0)
+	edbIdExist := make(map[int]bool)
+	for _, v := range excelCodes {
+		// 表格详情
+		detail, msg, e := GetExcelDetailInfoByUnicode(v)
+		if e != nil {
+			err = fmt.Errorf("GetExcelDetailInfoByExcelInfoId err: %s, errMsg: %s", e.Error(), msg)
+			return
+		}
+
+		// 自定义表格
+		if detail.Source == utils.TIME_TABLE {
+			jsonByte, e := json.Marshal(detail.TableData)
+			if e != nil {
+				err = fmt.Errorf("JSON格式化自定义表格数据失败, Err: %s", e.Error())
+				return
+			}
+			var tableData request.TableDataReq
+			if e = json.Unmarshal(jsonByte, &tableData); e != nil {
+				err = fmt.Errorf("解析自定义表格数据失败, Err: %s", e.Error())
+				return
+			}
+			for _, tv := range tableData.EdbInfoIdList {
+				if edbIdExist[tv] {
+					continue
+				}
+				edbIdExist[tv] = true
+				edbIds = append(edbIds, tv)
+			}
+		}
+
+		// 混合表格
+		if detail.Source == utils.MIXED_TABLE {
+			jsonByte, e := json.Marshal(detail.TableData)
+			if e != nil {
+				err = fmt.Errorf("JSON格式化混合表格数据失败, Err: %s", e.Error())
+				return
+			}
+			var tableData request.MixedTableReq
+			if e = json.Unmarshal(jsonByte, &tableData); e != nil {
+				err = fmt.Errorf("解析混合表格数据失败, Err: %s", e.Error())
+				return
+			}
+			if len(tableData.Data) > 0 {
+				for _, td := range tableData.Data {
+					for _, tv := range td {
+						if tv.EdbInfoId > 0 && !edbIdExist[tv.EdbInfoId] {
+							edbIdExist[tv.EdbInfoId] = true
+							edbIds = append(edbIds, tv.EdbInfoId)
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// GetExcelEdbBatchRefreshKey 获取批量刷新表格指标缓存key
+func GetExcelEdbBatchRefreshKey(source string, reportId, chapterId int) string {
+	if source == `` {
+		return ``
+	}
+
+	return fmt.Sprint("batch_refresh_excel_edb:", source, ":", reportId, ":", chapterId)
+}

+ 74 - 32
services/data/excel/mixed_table.go

@@ -113,7 +113,13 @@ func GetMixedTableCellData(mixedTableReq request.MixedTableReq) (newMixedTableCe
 		for i, cell := range row {
 			// 单元格是日期类型,且是日导入指标日期(指标库的最新日期)
 			if cell.DataType == request.DateDT && cell.DataTimeType == request.EdbDateDT {
-				if edbInfo, ok := edbInfoMap[cell.EdbInfoId]; ok {
+				// 指标id是在配置里面
+				var edbDateConfig request.EdbDateConf
+				err = json.Unmarshal([]byte(cell.Value), &edbDateConfig)
+				if err != nil {
+					return
+				}
+				if edbInfo, ok := edbInfoMap[edbDateConfig.EdbInfoId]; ok {
 					cell.ShowValue = edbInfo.EndDate
 					cell.DataTime = edbInfo.EndDate
 					config[k][i] = cell
@@ -145,36 +151,70 @@ func GetMixedTableCellData(mixedTableReq request.MixedTableReq) (newMixedTableCe
 					cell.ShowValue = edbInfo.EdbName
 				}
 			case request.InsertDataDT, request.PopInsertDataDT: // 数据类型
-				if cell.DataTime == `` {
-					// 指标的最新日期
-					if dateValList, ok := edbDataListMap[cell.EdbInfoId]; ok {
-						tmpLenData := len(dateValList)
-						if tmpLenData > 0 {
-							cellKeyVal[cell.Uid] = dateValList[tmpLenData-1].Value
-							cell.ShowValue = utils.FormatTableDataShowValue(dateValList[tmpLenData-1].Value)
-						}
+				// 数值先清空
+				cell.ShowValue = ``
+				//cell.Value = ``
+
+				// 日期关系配置不存在,则默认最新数据
+				if relationConf, ok := cellRelationConfMap[cell.Uid]; ok {
+					if relationConf.RelationDate.Key == `` {
+						// 日期关系配置未绑定
+						continue
+					}
+					// 配置
+					relationCell, ok := cellDataRelationMap[relationConf.RelationDate.Key]
+					if !ok {
+						// 找不到对应日期的单元格
+						continue
 					}
-				} else {
-					tmpDateList := strings.Split(cell.DataTime, "-")
-					tmpDateValMap := make(map[string]float64)
-					if len(tmpDateList) == 2 {
-						//月度数据
-						if dateValMap, ok := edbMonthDataListMap[cell.EdbInfoId]; ok {
-							tmpDateValMap = dateValMap
-						}
-					} else {
-						// 日度数据
-						if dateValMap, ok := edbDayDataListMap[cell.EdbInfoId]; ok {
-							tmpDateValMap = dateValMap
-						}
 
+					// 确实是找到了这个关联日期的单元格,那么通过日期重新获取数据值
+					tmpDateValMap := make(map[string]float64)
+					// 日度数据
+					if dateValMap, ok := edbDayDataListMap[cell.EdbInfoId]; ok {
+						tmpDateValMap = dateValMap
 					}
-					if val, ok2 := tmpDateValMap[cell.DataTime]; ok2 {
+
+					if val, ok2 := tmpDateValMap[relationCell.DataTime]; ok2 {
 						//cell.ShowValue = fmt.Sprint(val)
 						cellKeyVal[cell.Uid] = val
 						cell.ShowValue = utils.FormatTableDataShowValue(val)
 					}
+				} else {
+					// 如果不是取得一个关联的日期,那么就是指定日期
+					// 如果没有指定日期,则默认最新数据
+					if cell.DataTime == `` {
+						// 指标的最新日期
+						if dateValList, ok := edbDataListMap[cell.EdbInfoId]; ok {
+							tmpLenData := len(dateValList)
+							if tmpLenData > 0 {
+								cellKeyVal[cell.Uid] = dateValList[tmpLenData-1].Value
+								cell.ShowValue = utils.FormatTableDataShowValue(dateValList[tmpLenData-1].Value)
+							}
+						}
+					} else {
+						tmpDateList := strings.Split(cell.DataTime, "-")
+						tmpDateValMap := make(map[string]float64)
+						if len(tmpDateList) == 2 {
+							//月度数据
+							if dateValMap, ok := edbMonthDataListMap[cell.EdbInfoId]; ok {
+								tmpDateValMap = dateValMap
+							}
+						} else {
+							// 日度数据
+							if dateValMap, ok := edbDayDataListMap[cell.EdbInfoId]; ok {
+								tmpDateValMap = dateValMap
+							}
+
+						}
+						if val, ok2 := tmpDateValMap[cell.DataTime]; ok2 {
+							//cell.ShowValue = fmt.Sprint(val)
+							cellKeyVal[cell.Uid] = val
+							cell.ShowValue = utils.FormatTableDataShowValue(val)
+						}
+					}
 				}
+
 				calculateCellMap[cell.Uid] = Cell{
 					Column:   k,
 					Row:      i,
@@ -424,6 +464,7 @@ func handleConfig(configList [][]request.MixedTableCellDataReq) (newConfig [][]r
 				edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId)
 			case request.InsertDataDT, request.PopInsertDataDT: // 插值、弹框插值
 				dataEdbInfoIdList = append(dataEdbInfoIdList, cell.EdbInfoId)
+				edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId)
 			case request.InsertEdbCalculateDataDT: // 插入指标计算公式生成的值
 				var config request.CalculateConf
 				err = json.Unmarshal([]byte(cell.Value), &config)
@@ -434,6 +475,16 @@ func handleConfig(configList [][]request.MixedTableCellDataReq) (newConfig [][]r
 				dataEdbInfoIdList = append(dataEdbInfoIdList, cell.EdbInfoId)
 
 			case request.DateDT: // 日期类型
+				date, tmpErr, tmpErrMsg := handleDate(cell.DataTimeType, cell.Value)
+				if tmpErr != nil {
+					err = tmpErr
+					errMsg = tmpErrMsg
+					return
+				}
+				rowList[rk].DataTime = date
+				rowList[rk].ShowValue = date
+
+				// 指标日期类型的单元格需要额外将指标id取出来
 				if cell.DataTimeType == request.EdbDateDT {
 					var config request.EdbDateConf
 					err = json.Unmarshal([]byte(cell.Value), &config)
@@ -441,15 +492,6 @@ func handleConfig(configList [][]request.MixedTableCellDataReq) (newConfig [][]r
 						return
 					}
 					edbInfoIdList = append(edbInfoIdList, config.EdbInfoId)
-				} else {
-					date, tmpErr, tmpErrMsg := handleDate(cell.DataTimeType, cell.Value)
-					if tmpErr != nil {
-						err = tmpErr
-						errMsg = tmpErrMsg
-						return
-					}
-					rowList[rk].DataTime = date
-					rowList[rk].ShowValue = date
 				}
 			}
 		}

+ 1 - 1
services/data/future_good/chart_info.go

@@ -921,7 +921,7 @@ func FutureGoodChartInfoRefresh(chartInfoId int) (err error) {
 	}
 
 	// 批量刷新ETA指标
-	err, _ = data.EdbInfoRefreshAllFromBaseV3([]int{edbInfoMapping.EdbInfoId}, false, true)
+	err, _ = data.EdbInfoRefreshAllFromBaseV3([]int{edbInfoMapping.EdbInfoId}, false, true, false)
 	if err != nil {
 		return
 	}

+ 43 - 0
services/data/my_chart.go

@@ -0,0 +1,43 @@
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+// GetMyChartClassifyIdNumMap 我的图表-获取分类ID及图表数map
+func GetMyChartClassifyIdNumMap(adminId int) (chartsNumMap map[int]int, err error) {
+	chartsNumMap = make(map[int]int)
+
+	// 获取当前账号的不可见指标
+	chartIds := make([]int, 0)
+	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	charts, e := obj.GetAllChartListByAdminId(adminId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取不可见指标配置数据失败, err: %s", e.Error())
+		return
+	}
+	for _, v := range charts {
+		chartIds = append(chartIds, v.ChartInfoId)
+	}
+
+	cond := ``
+	pars := make([]interface{}, 0)
+	lenChart := len(chartIds)
+	if lenChart > 0 {
+		cond += ` AND d.chart_info_id NOT IN (` + utils.GetOrmInReplace(lenChart) + `) `
+		pars = append(pars, chartIds)
+	}
+
+	// 分类图表数
+	chartsNum, e := data_manage.GetMyChartClassifyIdAndNum(cond, pars)
+	if e != nil {
+		err = fmt.Errorf("获取分类图表数失败, err: %s", e.Error())
+		return
+	}
+	for _, v := range chartsNum {
+		chartsNumMap[v.MyChartClassifyId] = v.ChartNum
+	}
+	return
+}

+ 2 - 156
services/data/predict_edb_info.go

@@ -9,7 +9,6 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"strconv"
 	"strings"
 	"time"
@@ -399,7 +398,7 @@ func EditPredictEdbInfo(edbInfoId, classifyId int, edbName string, ruleList []re
 	AddOrEditEdbInfoToEs(edbInfoId)
 
 	// 刷新关联指标
-	go EdbInfoRefreshAllFromBaseV2(edbInfo.EdbInfoId, true)
+	go EdbInfoRefreshAllFromBaseV2(edbInfo.EdbInfoId, true, false)
 	return
 }
 
@@ -424,7 +423,7 @@ func RefreshPredictEdbInfo(edbInfoId int, refreshAll bool) (edbInfo *data_manage
 			return
 		}
 	}
-	err, isAsync = EdbInfoRefreshAllFromBaseV2(edbInfo.EdbInfoId, refreshAll)
+	err, isAsync = EdbInfoRefreshAllFromBaseV2(edbInfo.EdbInfoId, refreshAll, false)
 
 	return
 }
@@ -1127,159 +1126,6 @@ func GetPredictCalculateDataListByPredictEdbInfo(edbInfo *data_manage.EdbInfo, s
 	return
 }
 
-// GetCalculateByRuleByNineParams 获取预测规则9的计算参数
-func GetCalculateByRuleByNineParams(req request.RuleConfig) (formula string, edbInfoList []*data_manage.EdbInfo, edbInfoIdBytes []string, err error, errMsg string) {
-	formula = req.Value
-	formula = strings.Replace(formula, "(", "(", -1)
-	formula = strings.Replace(formula, ")", ")", -1)
-	formula = strings.Replace(formula, ",", ",", -1)
-	formula = strings.Replace(formula, "。", ".", -1)
-	formula = strings.Replace(formula, "%", "*0.01", -1)
-
-	//检验公式
-	var checkFormulaStr string
-	for _, tmpEdbInfoId := range req.EdbInfoIdArr {
-		checkFormulaStr += tmpEdbInfoId.FromTag + ","
-		edbInfoIdBytes = append(edbInfoIdBytes, tmpEdbInfoId.FromTag)
-	}
-	formulaMap := CheckFormula(formula)
-	for _, tmpFormula := range formulaMap {
-		if !strings.Contains(checkFormulaStr, tmpFormula) {
-			errMsg = "公式错误,请重新填写"
-			return
-		}
-	}
-
-	//关联的指标信息
-	edbInfoList = make([]*data_manage.EdbInfo, 0)
-
-	for _, tmpEdbInfoId := range req.EdbInfoIdArr {
-		fromEdbInfo, tmpErr := data_manage.GetEdbInfoById(tmpEdbInfoId.EdbInfoId)
-		if tmpErr != nil {
-			if tmpErr.Error() == utils.ErrNoRow() {
-				err = errors.New("指标 " + strconv.Itoa(tmpEdbInfoId.EdbInfoId) + " 不存在")
-			} else {
-				err = errors.New("获取指标失败:Err:" + tmpErr.Error())
-			}
-			errMsg = "数据计算失败"
-			return
-		}
-		edbInfoList = append(edbInfoList, fromEdbInfo)
-	}
-	ok, _ := CheckFormula2(edbInfoList, formulaMap, formula, edbInfoIdBytes)
-	if !ok {
-		errMsg = "生成计算指标失败,请使用正确的计算公式"
-		err = errors.New(errMsg)
-	}
-	return
-}
-
-// CalculateByRuleByNine 动态环差规则计算入库
-func CalculateByRuleByNine(formulaStr string, edbInfoList []*data_manage.EdbInfo, edbInfoIdBytes []string) (dataList []*data_manage.EdbDataList, err error) {
-	realSaveDataMap := make(map[string]map[int]float64)
-	saveDataMap := make(map[string]map[int]float64)
-	dateList := make([]string, 0) //日期
-
-	formulaStr = strings.ToUpper(formulaStr)
-	// 获取关联指标数据
-	for edbInfoIndex, v := range edbInfoList {
-		sourceDataList, _, _, tmpErr, _ := GetPredictDataListByPredictEdbInfo(v, "", "", false)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		dataMap := make(map[string]float64)
-		for _, dv := range sourceDataList {
-			// 实际数据
-			if val, ok := realSaveDataMap[dv.DataTime]; ok {
-				if _, ok := val[v.EdbInfoId]; !ok {
-					val[v.EdbInfoId] = dv.Value
-				}
-			} else {
-				temp := make(map[int]float64)
-				temp[v.EdbInfoId] = dv.Value
-				realSaveDataMap[dv.DataTime] = temp
-			}
-
-			// saveDataMap 待计算的数据
-			if val, ok := saveDataMap[dv.DataTime]; ok {
-				if _, ok := val[v.EdbInfoId]; !ok {
-					val[v.EdbInfoId] = dv.Value
-				}
-			} else {
-				temp2 := make(map[int]float64)
-				temp2[v.EdbInfoId] = dv.Value
-				saveDataMap[dv.DataTime] = temp2
-			}
-
-			// 以第一个指标的日期作为基准日期
-			if edbInfoIndex == 0 {
-				dateList = append(dateList, dv.DataTime)
-			}
-		}
-		item := new(CalculateItems)
-		item.EdbInfoId = v.EdbInfoId
-		item.DataMap = dataMap
-	}
-
-	//数据处理,将日期内不全的数据做补全
-	handleDateSaveDataMap(dateList, realSaveDataMap, saveDataMap, edbInfoList)
-
-	// 添加数据
-	dataList = make([]*data_manage.EdbDataList, 0)
-
-	// 计算规则
-	formulaMap := CheckFormula(formulaStr)
-
-	existDataMap := make(map[string]string)
-
-	for k, date := range dateList {
-		sv := saveDataMap[date]
-		//fmt.Println(date, sv)
-
-		formulaFormStr := ReplaceFormula(edbInfoList, sv, formulaMap, formulaStr, edbInfoIdBytes)
-		if formulaFormStr == `` {
-			//计算公式异常,那么就移除该指标
-			continue
-		}
-
-		//fmt.Println(fmt.Sprintf("formulaFormStr:%s", formulaFormStr))
-		expression := formula.NewExpression(formulaFormStr)
-		calResult, tmpErr := expression.Evaluate()
-		if tmpErr != nil {
-			// 分母为0的报错
-			if strings.Contains(tmpErr.Error(), "divide by zero") {
-				continue
-			}
-			err = errors.New("计算失败:Err:" + tmpErr.Error() + ";formulaStr:" + formulaFormStr)
-			return
-		}
-		calVal, tmpErr := calResult.Float64()
-		if tmpErr != nil {
-			err = errors.New("计算失败:获取计算值失败 Err:" + tmpErr.Error() + ";formulaStr:" + formulaFormStr)
-			fmt.Println(err)
-			return
-		}
-
-		saveValue, _ := decimal.NewFromFloat(calVal).RoundCeil(4).Float64() //utils.SubFloatToString(calVal, 4)
-		dataTime, _ := time.Parse(utils.FormatDate, date)
-		timestamp := dataTime.UnixNano() / 1e6
-
-		if _, existOk := existDataMap[date]; !existOk {
-			tmpPredictEdbRuleData := &data_manage.EdbDataList{
-				EdbDataId:     k,
-				EdbInfoId:     0,
-				DataTime:      date,
-				DataTimestamp: timestamp,
-				Value:         saveValue,
-			}
-			dataList = append(dataList, tmpPredictEdbRuleData)
-		}
-		existDataMap[date] = date
-	}
-	return
-}
-
 // ModifyPredictEdbBaseInfoBySourceEdb  根据来源ETA指标修改预测指标的基础信息
 func ModifyPredictEdbBaseInfoBySourceEdb(sourceEDdbInfo *data_manage.EdbInfo) {
 	list, err := data_manage.GetGroupPredictEdbBySourceEdbInfoId(sourceEDdbInfo.EdbInfoId)

+ 60 - 0
services/ppt.go

@@ -382,3 +382,63 @@ func SaveEnglishPPTReport(pptId, classifyIdFirst, classifyIdSecond int, title, a
 	reportCode = newCode
 	return
 }
+
+// UpdatePptEditing 更新PPT编辑状态
+func UpdatePptEditing(pptId, status, userId int, userName string, isEn bool) (ret ppt_english.PPTEditingCache, err error) {
+	if pptId <= 0 {
+		return
+	}
+	cacheKey := ""
+	if isEn {
+		cacheKey = fmt.Sprint(utils.CACHE_EN_PPT_EDITING, pptId)
+	} else {
+		cacheKey = fmt.Sprint(utils.CACHE_PPT_EDITING, pptId)
+	}
+
+	// 完成编辑
+	if status == 2 {
+		_ = utils.Rc.Delete(cacheKey)
+		return
+	}
+
+	// 读取缓存中的结果
+	var editor ppt_english.PPTEditingCache
+	strCache, _ := utils.Rc.RedisString(cacheKey)
+	fmt.Println(strCache)
+	if strCache != "" {
+		e := json.Unmarshal([]byte(strCache), &editor)
+		if e != nil {
+			err = fmt.Errorf("解析缓存内容失败: %s", e.Error())
+			return
+		}
+	}
+
+	// 标记编辑中
+	if status == 1 {
+		// 无人编辑, 写入缓存
+		if !editor.IsEditing {
+			ret.IsEditing = true
+			ret.AdminId = userId
+			ret.Editor = userName
+			ret.Tips = fmt.Sprintf("当前%s正在编辑PPT", userName)
+			b, _ := json.Marshal(ret)
+			utils.Rc.SetNX(cacheKey, string(b), 3*time.Minute)
+			return
+		}
+
+		// 有人编辑
+		if editor.IsEditing {
+			// 编辑用户与当前用户不一致, 返回编辑用户, 一致则更新缓存
+			if userId == editor.AdminId {
+				b, _ := json.Marshal(editor)
+				utils.Rc.Do("SETEX", cacheKey, int64(180), string(b))
+			}
+			ret = editor
+			return
+		}
+	} else {
+		// 默认查询
+		ret = editor
+	}
+	return
+}

+ 3 - 3
services/sandbox/sandbox.go

@@ -692,7 +692,7 @@ func GetSandboxClassifyListForMe(adminInfo system.Admin, resp *sandbox.SandboxCl
 	for _, v := range sandboxAll {
 		if _, ok := sandListMap[v.SandboxClassifyId]; !ok {
 			list := make([]*sandbox.SandboxClassifyItems, 0)
-			list  = append(list, v)
+			list = append(list, v)
 			sandListMap[v.SandboxClassifyId] = list
 		} else {
 			sandListMap[v.SandboxClassifyId] = append(sandListMap[v.SandboxClassifyId], v)
@@ -778,6 +778,7 @@ func AddSandboxV2(req request.AddAndEditSandboxV2, opUserId int, opUserName stri
 		CreateTime:        time.Now(),
 		SandboxClassifyId: req.SandboxClassifyId,
 		Sort:              0,
+		Style:             req.Style,
 	}
 
 	//新增沙盘
@@ -790,8 +791,7 @@ func AddSandboxV2(req request.AddAndEditSandboxV2, opUserId int, opUserName stri
 	return
 }
 
-
-func SandboxItemsMakeTree(allNode []*sandbox.SandboxClassifyItems, sandListMap map[int][]*sandbox.SandboxClassifyItems, sandboxClassifyId int) (nodeAll []*sandbox.SandboxClassifyItems){
+func SandboxItemsMakeTree(allNode []*sandbox.SandboxClassifyItems, sandListMap map[int][]*sandbox.SandboxClassifyItems, sandboxClassifyId int) (nodeAll []*sandbox.SandboxClassifyItems) {
 	for k := range allNode {
 		if len(allNode[k].Children) > 0 {
 			SandboxItemsMakeTree(allNode[k].Children, sandListMap, sandboxClassifyId)

+ 37 - 0
utils/common.go

@@ -6,6 +6,7 @@ import (
 	"crypto/hmac"
 	"crypto/md5"
 	"crypto/sha1"
+	"crypto/sha256"
 	"encoding/base64"
 	"encoding/hex"
 	"encoding/json"
@@ -25,6 +26,7 @@ import (
 	"os/exec"
 	"path"
 	"regexp"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -2195,6 +2197,41 @@ func GetLikeKeywordPars(pars []interface{}, keyword string, num int) (newPars []
 	return
 }
 
+func GetSign(nonce, timestamp, appId, secret string) (sign string) {
+	signStrMap := map[string]string{
+		"nonce":     nonce,
+		"timestamp": timestamp,
+		"appid":     appId,
+	}
+	keys := make([]string, 0, len(signStrMap))
+	for k := range signStrMap {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	var signStr string
+	for _, k := range keys {
+		signStr += k + "=" + signStrMap[k] + "&"
+	}
+	signStr = strings.Trim(signStr, "&")
+	fmt.Println("signStr:" + signStr)
+	sign = HmacSha256ToBase64(secret, signStr)
+	return
+}
+
+// HmacSha256ToBase64 将加密后的二进制转Base64字符串
+func HmacSha256ToBase64(key string, data string) string {
+	return base64.URLEncoding.EncodeToString(HmacSha256(key, data))
+}
+
+// HmacSha256 计算HmacSha256
+// key 是加密所使用的key
+// data 是加密的内容
+func HmacSha256(key string, data string) []byte {
+	mac := hmac.New(sha256.New, []byte(key))
+	_, _ = mac.Write([]byte(data))
+	return mac.Sum(nil)
+}
+
 func PascalToSnake(s string) string {
 	var result []rune
 

+ 18 - 0
utils/config.go

@@ -17,6 +17,7 @@ var (
 	MYSQL_URL_GL     string
 	MYSQL_LOG_URL    string
 	MYSQL_WEEKLY_URL string //用户主库
+	MYSQL_AI_URL     string //ETA-AI 数据库
 
 	REDIS_CACHE string       //缓存地址
 	Rc          *cache.Cache //redis缓存
@@ -204,6 +205,14 @@ var (
 // PythonUrlReport2Img 生成长图服务地址
 var PythonUrlReport2Img string
 
+// ETA-AI服务
+var EtaAiUrl string
+
+var (
+	EtaAppid  string
+	EtaSecret string
+)
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {
@@ -243,6 +252,11 @@ func init() {
 	// 用户主库
 	MYSQL_WEEKLY_URL = config["mysql_url_weekly"]
 
+	// 用户主库
+	MYSQL_WEEKLY_URL = config["mysql_url_weekly"]
+	//ETA-AI
+	MYSQL_AI_URL = config["mysql_url_ai"]
+
 	REDIS_CACHE = config["beego_cache"]
 	if len(REDIS_CACHE) <= 0 {
 		panic(any("redis链接参数没有配置"))
@@ -460,6 +474,10 @@ func init() {
 	// 生成长图服务地址
 	PythonUrlReport2Img = config["python_url_report2img"]
 
+	EtaAiUrl = config["eta_ai_url"]
+	EtaAppid = config["eta_appid"]
+	EtaSecret = config["eta_secret"]
+
 	// 初始化ES
 	initEs()
 }

+ 10 - 7
utils/constants.go

@@ -222,6 +222,8 @@ const (
 
 	CACHE_SMART_REPORT_EDITING  = "eta:smart_report:editing:" // 智能研报用户编辑中
 	CACHE_SMART_REPORT_SEND_MSG = "eta:smart_report:sending:" // 智能研报用户报告推送
+	CACHE_PPT_EDITING           = "eta:ppt:editing:"          // PPT用户编辑中
+	CACHE_EN_PPT_EDITING        = "eta:en_ppt:editing:"       // 英文PPT用户编辑中
 
 	CACHE_CREATE_REPORT_IMGPDF_QUEUE = "eta_report:report_img_pdf_queue" // 生成报告长图PDF队列
 	CACHE_EDB_TERMINAL_CODE_URL      = "edb:terminal_code:edb_code:"     // 指标与终端关系的缓存
@@ -263,13 +265,14 @@ const (
 const (
 	CHART_SOURCE_DEFAULT                         = 1
 	CHART_SOURCE_FUTURE_GOOD                     = 2
-	CHART_SOURCE_CORRELATION                     = 3 // 相关性图表
-	CHART_SOURCE_ROLLING_CORRELATION             = 4 // 滚动相关性图表
-	CHART_SOURCE_FUTURE_GOOD_PROFIT              = 5 // 商品利润曲线
-	CHART_SOURCE_LINE_EQUATION                   = 6 // 拟合方程图表
-	CHART_SOURCE_LINE_FEATURE_STANDARD_DEVIATION = 7 // 统计特征-标准差图表
-	CHART_SOURCE_LINE_FEATURE_PERCENTILE         = 8 // 统计特征-百分位图表
-	CHART_SOURCE_LINE_FEATURE_FREQUENCY          = 9 // 统计特征-频率分布图表
+	CHART_SOURCE_CORRELATION                     = 3  // 相关性图表
+	CHART_SOURCE_ROLLING_CORRELATION             = 4  // 滚动相关性图表
+	CHART_SOURCE_FUTURE_GOOD_PROFIT              = 5  // 商品利润曲线
+	CHART_SOURCE_LINE_EQUATION                   = 6  // 拟合方程图表
+	CHART_SOURCE_LINE_FEATURE_STANDARD_DEVIATION = 7  // 统计特征-标准差图表
+	CHART_SOURCE_LINE_FEATURE_PERCENTILE         = 8  // 统计特征-百分位图表
+	CHART_SOURCE_LINE_FEATURE_FREQUENCY          = 9  // 统计特征-频率分布图表
+	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 )
 
 // 批量配置图表的位置来源