浏览代码

Merge branch 'master' into feature/pool105_calculate_format

xyxie 6 天之前
父节点
当前提交
f1da4862c1
共有 85 个文件被更改,包括 3935 次插入653 次删除
  1. 37 0
      cache/llm.go
  2. 81 0
      controllers/data_manage/ai_predict_model/framework.go
  3. 162 0
      controllers/data_manage/ai_predict_model/index.go
  4. 64 8
      controllers/data_manage/chart_classify.go
  5. 193 0
      controllers/data_manage/chart_common.go
  6. 3 1
      controllers/data_manage/correlation/correlation_chart_info.go
  7. 4 1
      controllers/data_manage/cross_variety/chart_info.go
  8. 3 3
      controllers/data_manage/data_manage_permission/data_manage_permission.go
  9. 3 3
      controllers/data_manage/data_manage_permission/data_move.go
  10. 1 1
      controllers/data_manage/data_manage_permission/message.go
  11. 85 19
      controllers/data_manage/edb_info.go
  12. 4 1
      controllers/data_manage/future_good/future_good_chart_info.go
  13. 4 1
      controllers/data_manage/line_equation/line_chart_info.go
  14. 4 1
      controllers/data_manage/line_feature/chart_info.go
  15. 38 11
      controllers/data_manage/my_chart.go
  16. 6 3
      controllers/data_manage/predict_edb_info.go
  17. 8 2
      controllers/data_manage/range_analysis/chart_info.go
  18. 93 4
      controllers/english_report/report.go
  19. 6 2
      controllers/material/material.go
  20. 5 1
      controllers/ppt.go
  21. 81 3
      controllers/report.go
  22. 21 2
      controllers/report_chapter.go
  23. 101 21
      controllers/report_v2.go
  24. 5 1
      controllers/sandbox/sandbox.go
  25. 11 7
      controllers/smart_report/smart_report.go
  26. 4 3
      go.mod
  27. 2 9
      go.sum
  28. 81 0
      models/ai_predict_model/ai_predict_model_data.go
  29. 130 0
      models/ai_predict_model/ai_predict_model_framework.go
  30. 33 0
      models/ai_predict_model/ai_predict_model_framework_node.go
  31. 96 0
      models/ai_predict_model/ai_predict_model_index.go
  32. 26 2
      models/business_conf.go
  33. 81 16
      models/data_manage/chart_classify.go
  34. 6 0
      models/data_manage/chart_edb_mapping.go
  35. 10 0
      models/data_manage/chart_info.go
  36. 1 1
      models/data_manage/data_manage_permission/classify_no_auth_record.go
  37. 1 1
      models/data_manage/data_manage_permission/move.go
  38. 1 1
      models/data_manage/data_manage_permission/move_record.go
  39. 1 1
      models/data_manage/data_manage_permission/no_auth_record.go
  40. 1 1
      models/data_manage/edb_data_mysteel_chemical.go
  41. 22 7
      models/data_manage/edb_info.go
  42. 3 3
      models/data_manage/mysteel_chemical_classify.go
  43. 24 24
      models/data_manage/mysteel_chemical_index.go
  44. 7 6
      models/data_manage/request/mysteel_chemical_data.go
  45. 1 1
      models/db.go
  46. 20 0
      models/english_report.go
  47. 1 0
      models/ppt_english/ppt_english.go
  48. 39 1
      models/ppt_english/ppt_english_group_mapping.go
  49. 33 2
      models/ppt_v2_group_mapping.go
  50. 54 9
      models/report.go
  51. 134 0
      models/report/report_free_layout.go
  52. 6 3
      models/report_chapter.go
  53. 26 2
      models/report_v2.go
  54. 3 2
      models/smart_report/smart_report.go
  55. 1 1
      models/system/sys_user.go
  56. 45 0
      routers/commentsRouter.go
  57. 11 0
      routers/router.go
  58. 221 0
      services/ai_predict_model_index.go
  59. 9 11
      services/data/base_edb_lib.go
  60. 120 7
      services/data/chart_classify.go
  61. 47 67
      services/data/chart_info.go
  62. 19 50
      services/data/chart_info_excel_balance.go
  63. 1 1
      services/data/chart_theme.go
  64. 14 14
      services/data/data_manage_permission/data_move.go
  65. 1 1
      services/data/data_manage_permission/message.go
  66. 60 8
      services/data/edb_info.go
  67. 157 0
      services/dongwu_sms.go
  68. 1 0
      services/elastic.go
  69. 42 0
      services/english_report.go
  70. 113 0
      services/eta_forum/chart_classify.go
  71. 71 0
      services/eta_forum/eta_forum_hub_lib.go
  72. 6 2
      services/material/material.go
  73. 74 47
      services/ppt/ppt_english_group.go
  74. 89 185
      services/ppt/ppt_group.go
  75. 5 4
      services/report.go
  76. 5 1
      services/report_approve.go
  77. 549 21
      services/report_v2.go
  78. 228 36
      services/smart_report.go
  79. 2 0
      services/sms.go
  80. 5 0
      utils/business_conf.go
  81. 89 4
      utils/common.go
  82. 40 2
      utils/constants.go
  83. 2 0
      utils/redis.go
  84. 19 0
      utils/redis/cluster_redis.go
  85. 19 0
      utils/redis/standalone_redis.go

+ 37 - 0
cache/llm.go

@@ -0,0 +1,37 @@
+package cache
+
+import (
+	"eta/eta_mobile/utils"
+	"fmt"
+)
+
+type RagEtaReportOpOp struct {
+	Source          string
+	ReportId        int
+	ReportChapterId int
+}
+
+// RagEtaReportOpToCache
+// @Description: 将eta报告入知识库操作加入缓存
+// @author: Roc
+// @datetime 2025-04-07 15:05:22
+// @param reportId int
+// @param reportChapterId int
+// @param source string
+// @return bool
+func RagEtaReportOpToCache(reportId, reportChapterId int, source string) bool {
+	record := new(RagEtaReportOpOp)
+	record.Source = source
+	record.ReportId = reportId
+	record.ReportChapterId = reportChapterId
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_ETA_REPORT_KNOWLEDGE, record)
+
+		utils.FileLog.Info(fmt.Sprintf("将eta报告入知识库操作加入缓存 加入缓存 RagEtaReportOpToCache LPush: 操作类型:%s,报告id:%d,章节id:%d", source, reportId, reportChapterId))
+		if err != nil {
+			fmt.Println("RagEtaReportOpToCache LPush Err:" + err.Error())
+		}
+		return true
+	}
+	return false
+}

+ 81 - 0
controllers/data_manage/ai_predict_model/framework.go

@@ -0,0 +1,81 @@
+package ai_predict_model
+
+import (
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	aiPredictModel "eta/eta_mobile/models/ai_predict_model"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"strings"
+)
+
+// AiPredictModelFrameworkController 模型框架
+type AiPredictModelFrameworkController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 列表
+// @Description 列表
+// @Param   AdminId		query	int		false	"创建人ID"
+// @Param   Visibility	query	int		false	"范围: 0-所有; 1-私有; 2-公开"
+// @Param   Keyword		query	string	false	"关键词"
+// @Success 200 Ret=200 获取成功
+// @router /framework/list [get]
+func (c *AiPredictModelFrameworkController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	adminId, _ := c.GetInt("AdminId")
+	keyword := c.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+
+	frameworkOb := new(aiPredictModel.AiPredictModelFramework)
+
+	cond := ``
+	pars := make([]interface{}, 0)
+	if adminId > 0 {
+		cond += fmt.Sprintf(` AND %s = ?`, aiPredictModel.AiPredictModelFrameworkColumns.AdminId)
+		pars = append(pars, adminId)
+	}
+	if keyword != "" {
+		cond += fmt.Sprintf(` AND %s LIKE ?`, aiPredictModel.AiPredictModelFrameworkColumns.FrameworkName)
+		pars = append(pars, "%"+keyword+"%")
+	}
+
+	orderRule := `sort ASC, create_time DESC`
+	list, e := frameworkOb.GetItemsByCondition(cond, pars, []string{}, orderRule)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取框架列表失败, Err: " + e.Error()
+		return
+	}
+	resp := make([]*aiPredictModel.AiPredictModelFrameworkItem, 0)
+	for _, v := range list {
+		t := aiPredictModel.FormatAiPredictModelFramework2Item(v, make([]*aiPredictModel.AiPredictModelFrameworkNodeItem, 0))
+		if t.AdminId == sysUser.AdminId || utils.IsAdminRole(sysUser.RoleTypeCode) {
+			t.Button.OpButton = true
+			t.Button.DeleteButton = true
+			t.Button.MoveButton = true
+		}
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 162 - 0
controllers/data_manage/ai_predict_model/index.go

@@ -0,0 +1,162 @@
+package ai_predict_model
+
+import (
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndexController AI预测模型标的
+type AiPredictModelIndexController struct {
+	controllers.BaseAuthController
+}
+
+// SearchByEs
+// @Title 图表模糊搜索(从es获取)
+// @Description  图表模糊搜索(从es获取)
+// @Param   Keyword   query   string  true       "图表名称"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "来源,14:日度预测,15:月度预测,默认0:全部14+15"
+// @Success 200 {object} data_manage.ChartInfo
+// @router /chart/search_by_es [get]
+func (this *AiPredictModelIndexController) SearchByEs() {
+	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
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := this.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+	if keyword == "" {
+		keyword = this.GetString("KeyWord")
+		keyword = strings.TrimSpace(keyword)
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	source, _ := this.GetInt("Source")
+	sourceList := make([]int, 0)
+	if source <= 0 {
+		sourceList = append(sourceList, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY, utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY)
+	} else {
+		sourceList = append(sourceList, source)
+	}
+
+	var searchList []*data_manage.ChartInfoMore
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标(AI预测的指标为标的均可见)
+	noPermissionChartIdList := make([]int, 0)
+	//{
+	//	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	//	confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+	//	if err != nil && !utils.IsErrNoRow(err) {
+	//		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.ChartInfo
+			// 图表数据权限
+			tmp.HaveOperaAuth = true
+			//判断是否需要展示英文标识
+			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
+			}
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
+			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
+}

+ 64 - 8
controllers/data_manage/chart_classify.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_mobile/models/system"
 	"eta/eta_mobile/services/data"
 	"eta/eta_mobile/services/data/data_manage_permission"
+	"eta/eta_mobile/services/eta_forum"
 	"eta/eta_mobile/utils"
 	"fmt"
 	"time"
@@ -259,15 +260,26 @@ func (this *ChartClassifyController) ChartClassifyItems() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_DEFAULT)
+	isSelected, _ := this.GetInt("IsSelected")
+	var rootList []*data_manage.ChartClassifyItems
+	var err error
+	//if isSelected == utils.ChartClassifyIsSelected {
+	rootList, err = data_manage.GetChartClassifyByParentIdAndIsSelected(0, utils.CHART_SOURCE_DEFAULT, isSelected)
+	// } else {
+	// 	rootList, err = data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_DEFAULT)
+	// }
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
+	var classifyAll []*data_manage.ChartClassifyItems
+//	if isSelected == utils.ChartClassifyIsSelected {
+		classifyAll, err = data_manage.GetChartClassifyIsSelectedAll(utils.CHART_SOURCE_DEFAULT, isSelected)
+	// } else {
+	// 	classifyAll, err = data_manage.GetChartClassifyAll(utils.CHART_SOURCE_DEFAULT)
+	// }
 
-	classifyAll, err := data_manage.GetChartClassifyAll(utils.CHART_SOURCE_DEFAULT)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
@@ -488,6 +500,23 @@ func (this *ChartClassifyController) DeleteChartClassifyCheck() {
 			tipsMsg = "确认删除当前目录及包含的子目录吗"
 		}
 	}
+	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
+			}
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+		if chartInfo.ForumChartInfoId > 0 {
+			deleteStatus = 3
+			tipsMsg = "删除后,该图表将从ETA投研资源库同步删除,影响客户的查看权限,是否确认删除?"
+		}
+	}
 	if deleteStatus == 0 {
 		tipsMsg = "可删除,进行删除操作"
 	}
@@ -660,6 +689,10 @@ func (this *ChartClassifyController) DeleteChartClassify() {
 			// 删除MY ETA 图表 es数据
 			//go data.EsDeleteMyChartInfoByChartInfoId(req.ChartInfoId)
 			go data.EsDeleteMyChartInfoByMyChartIds(myIds)
+			
+			if chartInfo.ForumChartInfoId > 0 {
+				go eta_forum.DeleteChartByForumChartInfoId(chartInfo.ForumChartInfoId)
+			}
 		}
 
 		var condition string
@@ -779,7 +812,9 @@ func (this *ChartClassifyController) ChartClassifyMove() {
 		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
 		return
 	}
-
+	oldParentId := chartClassifyInfo.ParentId
+	oldLevelPath := chartClassifyInfo.LevelPath
+	oldSelected := chartClassifyInfo.IsSelected
 	// 校验移动的父级目录下是否有重名分类
 	exists, e := data_manage.GetChartClassifyByParentIdAndName(req.ParentClassifyId, chartClassifyInfo.ChartClassifyName, req.ClassifyId)
 	if e != nil && e.Error() != utils.ErrNoRow() {
@@ -816,15 +851,18 @@ func (this *ChartClassifyController) ChartClassifyMove() {
 		return
 	}
 	updateCol := make([]string, 0)
-
-	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
-	if chartClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
-		parentChartClassifyInfo, err := data_manage.GetChartClassifyById(req.ParentClassifyId)
+	var parentChartClassifyInfo *data_manage.ChartClassify
+	if req.ParentClassifyId >0 {
+		parentChartClassifyInfo, err = data_manage.GetChartClassifyById(req.ParentClassifyId)
 		if err != nil {
 			br.Msg = "移动失败"
 			br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
 			return
 		}
+	}
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if chartClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+		
 		chartClassifyInfo.ParentId = parentChartClassifyInfo.ChartClassifyId
 		chartClassifyInfo.Level = parentChartClassifyInfo.Level + 1
 		chartClassifyInfo.ModifyTime = time.Now()
@@ -896,6 +934,24 @@ func (this *ChartClassifyController) ChartClassifyMove() {
 			br.ErrMsg = "修改失败,Err:" + err.Error()
 			return
 		}
+		//更新分类的level_path
+		if oldParentId != req.ParentClassifyId {
+			if oldLevelPath != "" {
+				if err = data.UpdateChartClassifyLevelPathWithChildren(chartClassifyInfo, parentChartClassifyInfo, oldParentId, oldLevelPath); err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "更新分类level_path失败,Err:" + err.Error()
+					return
+				}
+			}
+		}
+		if chartClassifyInfo.Source == utils.CHART_SOURCE_DEFAULT {
+			if err = data.UpdateChildClassifySelection(chartClassifyInfo, parentChartClassifyInfo, oldSelected); err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "更新子目录精选标识失败,Err:" + err.Error()
+				return
+			}
+			go eta_forum.ChartClassifySaveBatch(chartClassifyInfo.Source)
+		}
 	}
 	br.Ret = 200
 	br.Success = true

+ 193 - 0
controllers/data_manage/chart_common.go

@@ -9,12 +9,15 @@ import (
 	"eta/eta_mobile/controllers/data_manage/line_feature"
 	"eta/eta_mobile/controllers/data_manage/range_analysis"
 	"eta/eta_mobile/models"
+	aiPredictModel "eta/eta_mobile/models/ai_predict_model"
 	"eta/eta_mobile/models/data_manage"
 	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/services"
 	"eta/eta_mobile/services/data"
 	"eta/eta_mobile/services/data/excel"
 	"eta/eta_mobile/utils"
 	"fmt"
+	"strings"
 	"time"
 )
 
@@ -179,6 +182,29 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY:
+		resp, isOk, msg, errMsg := GetAiPredictChartInfoDetailFromUniqueCode(chartInfo, isCache)
+		if !isOk {
+			if strings.Contains(errMsg, utils.ErrNoRow()) {
+				endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+				resp.EdbInfoList = endInfoList
+				resp.ChartInfo = chartInfo
+				resp.Status = false
+
+				br.Data = resp
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
+			}
+			br.Msg = msg
+			br.ErrMsg = errMsg
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
 	default:
 		br.Msg = "错误的图表"
 		br.ErrMsg = "错误的图表"
@@ -228,3 +254,170 @@ func getBalanceChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoVie
 
 	return
 }
+
+// GeneralChartToken
+// @Title 根据图表唯一code生成token
+// @Description 根据编码获取图表详情接口
+// @Param   UniqueCode   query   string  true       "图表/表格唯一编码"
+// @Param   Source   query   string  true       "来源,枚举值:chart、table"
+// @Success 200 {object} data_manage.ChartInfoDetailFromUniqueCodeResp
+// @router /chart_info/common/general_token [get]
+func (this *ChartInfoController) GeneralChartToken() {
+	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
+	}
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,uniqueCode is empty"
+		return
+	}
+	source := this.GetString("Source", "chart")
+
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+
+	var token string
+	if businessConf.ConfVal == `true` {
+		// 缓存key
+		sourceType := source
+		if source == `table` {
+			sourceType = source
+		}
+		token, err = services.GeneralChartToken(sourceType, uniqueCode)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败"
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = token
+	return
+}
+
+// GetAiPredictChartInfoDetailFromUniqueCode 根据编码获取AI预测模型图表详情
+func GetAiPredictChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
+	var err error
+	msg = "获取成功"
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("UniqueCode获取图表详情失败, %v", err)
+			msg = "获取失败"
+			errMsg = fmt.Sprintf(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	if chartInfo == nil {
+		err = fmt.Errorf("图表信息不存在")
+		return
+	}
+	if chartInfo.Source != utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY && chartInfo.Source != utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY {
+		err = fmt.Errorf("图表来源有误, Source: %d", chartInfo.Source)
+		return
+	}
+	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+
+	// 获取图表标的
+	edbMappings, e := data_manage.GetChartEdbMappingsByChartInfoId(chartInfo.ChartInfoId)
+	if e != nil {
+		err = fmt.Errorf("获取图表指标关联失败, %v", e)
+		return
+	}
+	if len(edbMappings) == 0 {
+		err = fmt.Errorf("图表指标关联不存在, %v", e)
+		return
+	}
+	indexId := edbMappings[0].EdbInfoId
+	if indexId <= 0 {
+		err = fmt.Errorf("图表标的有误")
+		return
+	}
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		err = fmt.Errorf("获取图表标的失败, %v", e)
+		return
+	}
+	if indexItem != nil && indexItem.AiPredictModelIndexId <= 0 {
+		err = fmt.Errorf("图表标的不存在, IndexId: %d", indexId)
+		return
+	}
+
+	// 获取标的数据
+	indexData := make([]*aiPredictModel.AiPredictModelData, 0)
+	dataSource := aiPredictModel.ModelDataSourceDaily
+	if chartInfo.Source == utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY {
+		dataSource = aiPredictModel.ModelDataSourceMonthly
+	}
+	dataOb := new(aiPredictModel.AiPredictModelData)
+	dataCond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().IndexCode)
+	dataPars := make([]interface{}, 0)
+	dataPars = append(dataPars, indexItem.IndexCode)
+	list, e := dataOb.GetItemsByCondition(dataCond, dataPars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+	if e != nil {
+		err = fmt.Errorf("获取标的数据失败, %v", e)
+		return
+	}
+	for _, v := range list {
+		if v.Source == dataSource {
+			indexData = append(indexData, v)
+			continue
+		}
+	}
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if chartData, e := utils.Rc.RedisBytes(key); e == nil {
+				e = json.Unmarshal(chartData, &resp)
+				if e != nil || resp == nil {
+					return
+				}
+				isOk = true
+				return
+			}
+		}
+	}
+
+	// 图表详情
+	chartDetail, e := services.GetAiPredictChartDetailByData(indexItem, indexData, dataSource)
+	if e != nil {
+		err = fmt.Errorf("获取图表详情失败, %v", e)
+		return
+	}
+	resp.ChartInfo = chartDetail.ChartInfo
+	resp.EdbInfoList = chartDetail.EdbInfoList
+	resp.XEdbIdValue = chartDetail.XEdbIdValue
+	resp.YDataList = chartDetail.YDataList
+	resp.XDataList = chartDetail.XDataList
+	resp.BarChartInfo = chartDetail.BarChartInfo
+	resp.CorrelationChartInfo = chartDetail.CorrelationChartInfo
+	resp.DataResp = chartDetail.DataResp
+	resp.Status = true
+
+	if utils.Re == nil {
+		jsonData, _ := json.Marshal(resp)
+		_ = utils.Rc.Put(key, jsonData, 10*time.Minute)
+	}
+	isOk = true
+	return
+}

+ 3 - 1
controllers/data_manage/correlation/correlation_chart_info.go

@@ -516,7 +516,9 @@ func (this *CorrelationChartInfoController) List() {
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		likeKey := `%` + keyword + `%`
+		condition += ` AND  ( chart_name LIKE ? )`
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的

+ 4 - 1
controllers/data_manage/cross_variety/chart_info.go

@@ -91,7 +91,10 @@ func (c *ChartInfoController) List() {
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		likeKey := `%` + keyword + `%`
+		//condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		condition += ` AND  ( chart_name LIKE ? )`
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的

+ 3 - 3
controllers/data_manage/data_manage_permission/data_manage_permission.go

@@ -203,7 +203,7 @@ func (c *DataMangePermissionController) SetEdbChartClassifyPermission() {
 // GetEdbChartPermission
 // @Title 指标/图表/表格权限设置接口
 // @Description 指标/图表/表格权限设置接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   DataId   query   int  false       "资产id"
 // @Success 200 {object} data_manage.ChartListResp
@@ -262,7 +262,7 @@ func (c *DataMangePermissionController) GetEdbChartPermission() {
 // GetEdbChartClassifyPermission
 // @Title 获取指标/图表/表格分类权限设置接口
 // @Description 获取指标/图表/表格分类权限设置接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   UserId   query   int  false       "用户id"
 // @Success 200 {object} data_manage.ChartListResp
@@ -336,7 +336,7 @@ func (c *DataMangePermissionController) GetEdbChartClassifyPermission() {
 // GetEdbChartNoPermission
 // @Title 根据资产id获取其分类没有权限的用户id列表接口
 // @Description 根据资产id获取其分类没有权限的用户id列表接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   DataId   query   int  false       "资产id"
 // @Success 200 {object} data_manage.ChartListResp

+ 3 - 3
controllers/data_manage/data_manage_permission/data_move.go

@@ -13,7 +13,7 @@ import (
 // EdbChartClassifyList
 // @Title 获取指标/图表分类列表数据接口
 // @Description 获取指标/图表分类列表数据接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)"
 // @Success 200 {object} data_manage.ChartListResp
 // @router /edb_chart/classify [get]
@@ -63,7 +63,7 @@ func (c *DataMangePermissionController) EdbChartClassifyList() {
 // SecretEdbChartClassifyList
 // @Title 获取涉密的指标/图表分类列表数据接口
 // @Description 获取指标/图表分类列表数据接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)"
 // @Success 200 {object} data_manage.ChartListResp
 // @router /edb_chart/classify/secret [get]
@@ -133,7 +133,7 @@ func removeNodesWithNoJoinPermissionAndEmptyChildRecursively(nodes []*data_manag
 // @Description 获取指标/图表创建人列表数据接口
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   Keyword   query   string  false       "关键字,code或者名称"
 // @Param   Classify   query   string  false       "分类id"

+ 1 - 1
controllers/data_manage/data_manage_permission/message.go

@@ -172,7 +172,7 @@ func (c *DataMangePermissionController) MessageRead() {
 // @Param   MessageId			query	int		true	"消息ID"
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Success 200 {object} data_manage_permission.MessageDetailListResp
 // @router /message/detail [get]

+ 85 - 19
controllers/data_manage/edb_info.go

@@ -58,6 +58,13 @@ func (this *EdbInfoController) EdbInfoSearch() {
 	stockCode := this.GetString("StockCode")
 	frequency := this.GetString("Frequency")
 
+	// 日期序列参数
+	period := this.GetString("Period")
+	days := this.GetString("Days")
+	priceAdj := this.GetString("PriceAdj")
+	extraPars := this.GetString("ExtraPars")
+	extraPars = strings.TrimSpace(extraPars)
+
 	if source <= 0 {
 		br.Msg = "无效的数据来源"
 		return
@@ -82,7 +89,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 	var item *data_manage.EdbInfo
 	var err error
 	if source == utils.DATA_SOURCE_GL {
-		// 如果是钢联的话,那么就先判断是不是存在钢联化工
+		// 如果是钢联的话,那么就先判断是不是存在上海钢联
 		item, err = data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, edbCode)
 		if err != nil {
 			if err.Error() != utils.ErrNoRow() {
@@ -106,7 +113,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 			}
 			err = nil
 
-			// 如果在钢联来源也没找到,那么就需要再判断下是否处于 《钢联化工》 数据源里面
+			// 如果在钢联来源也没找到,那么就需要再判断下是否处于 《上海钢联》 数据源里面
 			if item == nil {
 				tmpInfo, err := data_manage.GetBaseFromMysteelChemicalIndexByCode(edbCode)
 				if err != nil {
@@ -220,7 +227,13 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					br.Msg = "请输入指标代码"
 					return
 				}
-				respItem, err := data.AddEdbDataThsDs(source, stockCode, edbCode)
+				// 日期序列参数校验
+				pass, tips := data.CheckWindThsDsParams(source, subSource, period, days, priceAdj)
+				if !pass {
+					br.Msg = tips
+					return
+				}
+				respItem, err := data.AddEdbDataThsDs(source, stockCode, edbCode, extraPars, days, period)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -299,7 +312,13 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					br.Msg = "请输入指标代码"
 					return
 				}
-				respItem, err := data.AddEdbDataWindWsd(source, stockCode, edbCode)
+				// 日期序列参数校验
+				pass, tips := data.CheckWindThsDsParams(source, subSource, period, days, priceAdj)
+				if !pass {
+					br.Msg = tips
+					return
+				}
+				respItem, err := data.AddEdbDataWindWsd(source, stockCode, edbCode, days, period, priceAdj)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1279,7 +1298,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				}
 				isAdd = true
 			}
-		} else if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL { //钢联化工
+		} else if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL { //上海钢联
 			dataItems, err := data_manage.GetEdbDataAllByEdbCode(edbCode, source, subSource, utils.EDB_DATA_LIMIT)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "获取失败"
@@ -1880,6 +1899,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 			}
 		}
 		if isAdd {
+			// 日期序列
 			if subSource == utils.DATA_SUB_SOURCE_DATE {
 				if source == utils.DATA_SOURCE_WIND {
 					//wsdData := windWsdRespItem.Data
@@ -1946,8 +1966,11 @@ func (this *EdbInfoController) EdbInfoSearch() {
 
 					stockList := make([]*data_manage.StockInfo, 0)
 					edbCodeArr := strings.Split(edbCode, ",")
+					// 指标编码后缀,默认值则无需拼接
+					suffix := utils.GetWindWsdIndexCodeSuffix(period, days, priceAdj)
+
 					for _, v := range edbCodeArr {
-						indexCode := utils.WindDbWsd + stockCode + v
+						indexCode := utils.WindDbWsd + stockCode + v + suffix
 
 						dataList, err := data_manage.GetEdbDataAllByEdbCodeAndSubSource(indexCode, source, utils.DATA_SUB_SOURCE_DATE, utils.EDB_DATA_LIMIT)
 						if err != nil && err.Error() != utils.ErrNoRow() {
@@ -1959,6 +1982,9 @@ func (this *EdbInfoController) EdbInfoSearch() {
 						stockInfo := new(data_manage.StockInfo)
 						stockInfo.StockCode = stockCode
 						stockInfo.EdbCode = v
+						stockInfo.Days = days
+						stockInfo.Period = period
+						stockInfo.PriceAdj = priceAdj
 						stockInfo.DataList = dataList
 						stockList = append(stockList, stockInfo)
 					}
@@ -1966,8 +1992,11 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				} else if source == utils.DATA_SOURCE_THS {
 					stockList := make([]*data_manage.StockInfo, 0)
 					edbCodeArr := strings.Split(edbCode, ",")
+					// 指标编码后缀,默认值则无需拼接
+					suffix := utils.GetThsDsIndexCodeSuffix(period, days)
+
 					for _, v := range edbCodeArr {
-						indexCode := utils.ThsDs + stockCode + v
+						indexCode := utils.ThsDs + stockCode + v + suffix
 
 						dataList, err := data_manage.GetEdbDataAllByEdbCodeAndSubSource(indexCode, source, utils.DATA_SUB_SOURCE_DATE, utils.EDB_DATA_LIMIT)
 						if err != nil && err.Error() != utils.ErrNoRow() {
@@ -1979,6 +2008,8 @@ func (this *EdbInfoController) EdbInfoSearch() {
 						stockInfo := new(data_manage.StockInfo)
 						stockInfo.StockCode = stockCode
 						stockInfo.EdbCode = v
+						stockInfo.Days = days
+						stockInfo.Period = period
 						stockInfo.DataList = dataList
 						stockList = append(stockList, stockInfo)
 					}
@@ -2100,9 +2131,11 @@ func (this *EdbInfoController) EdbInfoList() {
 	}
 
 	if keyWord != "" {
-		condition += ` AND  ( edb_code LIKE '%` + keyWord + `%'  OR  edb_name LIKE '%` + keyWord + `%' )`
-		//pars = append(pars, keyWord)
-		//pars = append(pars, keyWord)
+		likeKey := `%` + keyWord + `%`
+		//condition += ` AND  ( edb_code LIKE '%` + keyWord + `%'  OR  edb_name LIKE '%` + keyWord + `%' )`
+		condition += ` AND  ( edb_code LIKE ?  OR  edb_name LIKE ? )`
+		pars = append(pars, likeKey)
+		pars = append(pars, likeKey)
 	}
 
 	resp := new(data_manage.EdbInfoListResp)
@@ -2289,9 +2322,9 @@ func (this *EdbInfoController) EdbInfoAdd() {
 	//	return
 	//}
 
-	// 兼容钢联与钢联化工数据
+	// 兼容钢联与上海钢联数据
 	if source == utils.DATA_SOURCE_GL {
-		// 如果是钢联的话,那么就先判断是不是存在钢联化工
+		// 如果是钢联的话,那么就先判断是不是存在上海钢联
 		tmpInfo, err := data_manage.GetBaseFromMysteelChemicalIndexByCode(req.EdbCode)
 		if err != nil {
 			if err.Error() != utils.ErrNoRow() {
@@ -3233,7 +3266,11 @@ func (this *EdbInfoController) EdbInfoFilter() {
 		if filterSource == 4 {
 			condition += ` AND edb_type = 1 `
 		}
-		conditionWhere := `AND  ( edb_code LIKE '%` + v + `%'  OR  edb_name LIKE '%` + v + `%' )`
+		//conditionWhere := `AND  ( edb_code LIKE '%` + v + `%'  OR  edb_name LIKE '%` + v + `%' )`
+		likeKey := `%` + v + `%`
+		conditionWhere := `AND  ( edb_code LIKE ?  OR  edb_name LIKE ? )`
+		pars = append(pars, likeKey)
+		pars = append(pars, likeKey)
 
 		condition += conditionWhere + baseCondition
 		newEdbInfoList, err := data_manage.GetEdbInfoFilter(condition, pars)
@@ -5073,7 +5110,7 @@ func (this *EdbInfoController) GetEdbBeforeAndAfterDateData() {
 // EdbChartAdminList
 // @Title 获取创建人员分组
 // @Description 获取创建人员分组
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库"
 // @Success 200 {object} company.DepartmentGroupSellersResp
 // @router /edb_chart/adminList [get]
 func (this *EdbInfoController) EdbChartAdminList() {
@@ -5612,6 +5649,11 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 	subSource, _ := this.GetInt("SubSource")
 	stockCode := this.GetString("StockCode")
 
+	// 日期序列额外参数
+	period := this.GetString("Period")
+	days := this.GetString("Days")
+	priceAdj := this.GetString("PriceAdj")
+
 	if source <= 0 {
 		br.Msg = "无效的数据来源"
 		return
@@ -5630,16 +5672,24 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 		br.Msg = "无效的数据库来源"
 		return
 	}
+	pass, tips := data.CheckWindThsDsParams(source, subSource, period, days, priceAdj)
+	if !pass {
+		br.Msg = tips
+		return
+	}
 
 	var indexCodeArr []string
 	edbCodeArr := strings.Split(edbCode, ",")
 	stockCodeArr := strings.Split(stockCode, ",")
 
-	var prefix string
+	// 指标编码前后缀
+	var prefix, suffix string
 	if source == utils.DATA_SOURCE_WIND {
 		prefix = utils.WindDbWsd
+		suffix = utils.GetWindWsdIndexCodeSuffix(period, days, priceAdj)
 	} else if source == utils.DATA_SOURCE_THS {
 		prefix = utils.ThsDs
+		suffix = utils.GetThsDsIndexCodeSuffix(period, days)
 	} else {
 		br.Msg = "来源错误"
 		br.ErrMsg = "来源错误"
@@ -5647,7 +5697,7 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 	}
 	for _, sv := range stockCodeArr {
 		for _, ev := range edbCodeArr {
-			indexCode := prefix + sv + ev
+			indexCode := prefix + sv + ev + suffix
 			indexCodeArr = append(indexCodeArr, indexCode)
 		}
 	}
@@ -5692,7 +5742,7 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 	br.Data = resp
 }
 
-// EdbInfoAdd
+// EdbInfoBatchAdd
 // @Title 指标批量保存接口
 // @Description 指标批量保存接口
 // @Param	request	body data_manage.BatchAddEdbInfoReq true "type json string"
@@ -5795,11 +5845,13 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 		edbInfoItem.Source = v.Source
 		switch v.Source {
 		case utils.DATA_SOURCE_WIND:
+			suffix := utils.GetWindWsdIndexCodeSuffix(v.Period, v.Days, v.PriceAdj)
 			edbInfoItem.SourceName = "wind"
-			edbInfoItem.EdbCode = utils.WindDbWsd + v.StockCode + v.EdbCode
+			edbInfoItem.EdbCode = utils.WindDbWsd + v.StockCode + v.EdbCode + suffix
 		case utils.DATA_SOURCE_THS:
+			suffix := utils.GetThsDsIndexCodeSuffix(v.Period, v.Days)
 			edbInfoItem.SourceName = "ths"
-			edbInfoItem.EdbCode = utils.ThsDs + v.StockCode + v.EdbCode
+			edbInfoItem.EdbCode = utils.ThsDs + v.StockCode + v.EdbCode + suffix
 		}
 		edbInfoItem.SubSource = utils.DATA_SUB_SOURCE_DATE
 		edbInfoItem.SubSourceName = "日期序列"
@@ -5815,6 +5867,20 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 		edbInfoItem.IndicatorCode = v.EdbCode
 		edbInfoItem.StockCode = v.StockCode
 
+		// 日期序列参数
+		var extra data_manage.EdbInfoExtra
+		extra.Days = v.Days
+		extra.Period = v.Period
+		extra.PriceAdj = v.PriceAdj
+		extra.ApiExtraPars = v.ApiExtraPars
+		b, e := json.Marshal(extra)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("额外参数JSON格式化失败, %v", e)
+			return
+		}
+		edbInfoItem.Extra = string(b)
+
 		// 指标入库
 		edbInfo, err, errMsg, isSendEmail := data.EdbInfoWsdAdd(edbInfoItem)
 		if err != nil {

+ 4 - 1
controllers/data_manage/future_good/future_good_chart_info.go

@@ -89,7 +89,10 @@ func (this *FutureGoodChartInfoController) ChartList() {
 		//pars = append(pars, chartClassifyId)
 	}
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		likeKey := `%` + keyword + `%`
+		//condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		condition += ` AND  ( chart_name LIKE ? )`
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的

+ 4 - 1
controllers/data_manage/line_equation/line_chart_info.go

@@ -595,7 +595,10 @@ func (this *LineEquationChartInfoController) List() {
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		//condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		likeKey := `%` + keyword + `%`
+		condition += ` AND  ( chart_name LIKE ? )`
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的

+ 4 - 1
controllers/data_manage/line_feature/chart_info.go

@@ -1639,7 +1639,10 @@ func (this *LineFeaturesChartInfoController) List() {
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		likeKey := `%` + keyword + `%`
+		//condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		condition += ` AND  ( chart_name LIKE ? )`
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的

+ 38 - 11
controllers/data_manage/my_chart.go

@@ -10,10 +10,12 @@ import (
 	"eta/eta_mobile/services/data/data_manage_permission"
 	"eta/eta_mobile/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // 我的图库
@@ -64,23 +66,45 @@ func (this *MyChartController) ChartList() {
 
 	var condition string
 	var pars []interface{}
-
+	// 是否显示精选资源
+	isSelected, _ := this.GetInt("IsSelected")
 	// 普通图表
 	condition += ` AND source = ? `
 	pars = append(pars, utils.CHART_SOURCE_DEFAULT)
+	chartClassifyIds := make([]int, 0)
+	var classifyList []*data_manage.ChartClassifyItems
+	var err error
+
+	classifyList, err = data_manage.GetChartClassifyAllBySourceIsSelected(utils.CHART_SOURCE_DEFAULT, isSelected)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", err)
+		return
+	}
 
 	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
+		parents := data.GetChartClassifyChildrenRecursive(classifyList, chartClassifyId)
+		sort.Slice(parents, func(i, j int) bool {
+			return parents[i].Level < parents[i].Level
+		})
+		for _, v := range parents {
+			chartClassifyIds = append(chartClassifyIds, v.ChartClassifyId)
+		}
+		condition += " AND chart_classify_id IN (" + utils.GetOrmInReplace(len(chartClassifyIds)) + ") "
+		pars = append(pars, chartClassifyIds)
+	} else if isSelected >= 0 {
+		for _, v := range classifyList {
+			chartClassifyIds = append(chartClassifyIds, v.ChartClassifyId)
 		}
-		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
-		//pars = append(pars, chartClassifyId)
+		condition += " AND chart_classify_id IN (" + utils.GetOrmInReplace(len(chartClassifyIds)) + ") "
+		pars = append(pars, chartClassifyIds)
 	}
 	if keyWord != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyWord + `%' OR chart_name_en LIKE '%` + keyWord + `%' )`
+		likeKey := `%` + keyWord + `%`
+		//condition += ` AND  ( chart_name LIKE '%` + keyWord + `%' OR chart_name_en LIKE '%` + keyWord + `%' )`
+		condition += ` AND  ( chart_name LIKE ? OR chart_name_en LIKE ? )`
+		pars = append(pars, likeKey)
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的
@@ -1659,7 +1683,10 @@ func (this *MyChartController) MyChartSearch() {
 
 	keyWord := this.GetString("KeyWord")
 	if keyWord != "" {
-		condition += ` AND chart_name LIKE '%` + keyWord + `%' `
+		//condition += ` AND chart_name LIKE '%` + keyWord + `%' `
+		condition += ` AND chart_name LIKE ? `
+		likeKey := `%` + keyWord + `%`
+		pars = append(pars, likeKey)
 	}
 
 	//获取图表信息

+ 6 - 3
controllers/data_manage/predict_edb_info.go

@@ -226,9 +226,12 @@ func (this *PredictEdbInfoController) List() {
 	}
 
 	if keyWord != "" {
-		condition += ` AND  ( edb_code LIKE '%` + keyWord + `%'  OR  edb_name LIKE '%` + keyWord + `%' )`
-		//pars = append(pars, keyWord)
-		//pars = append(pars, keyWord)
+
+		likeKey := `%` + keyWord + `%`
+
+		condition += ` AND  ( edb_code LIKE ?  OR  edb_name LIKE ? )`
+		pars = append(pars, likeKey)
+		pars = append(pars, likeKey)
 	}
 
 	resp := data_manage.EdbInfoListResp{}

+ 8 - 2
controllers/data_manage/range_analysis/chart_info.go

@@ -762,12 +762,18 @@ func (this *RangeChartChartInfoController) List() {
 	if keyword != "" {
 		//将关键词按照空格分割
 		keywords := strings.Split(keyword, " ")
-		condition += ` AND  ( chart_name LIKE '%` + keywords[0] + `%' `
+		//condition += ` AND  ( chart_name LIKE '%` + keywords[0] + `%' `
+		likeKey := `%` + keywords[0] + `%`
+		condition += ` AND  ( chart_name LIKE ? `
+		pars = append(pars, likeKey)
 		for k, key := range keywords {
 			if k == 0 {
 				continue
 			}
-			condition += ` OR chart_name LIKE '%` + key + `%' `
+			//condition += ` OR chart_name LIKE '%` + key + `%' `
+			likeKey := `%` + key + `%`
+			condition += ` OR chart_name LIKE ? `
+			pars = append(pars, likeKey)
 		}
 		condition += ` )`
 	}

+ 93 - 4
controllers/english_report/report.go

@@ -64,6 +64,8 @@ func (this *EnglishReportController) Add() {
 
 	var contentSub string
 	if req.Content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
+
 		content, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -181,6 +183,7 @@ func (this *EnglishReportController) Edit() {
 	}
 	var contentSub string
 	if req.Content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		content, e := services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
@@ -298,6 +301,17 @@ func (this *EnglishReportController) Detail() {
 	item.Content = html.UnescapeString(item.Content)
 	item.ContentSub = html.UnescapeString(item.ContentSub)
 
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+	if businessConf.ConfVal == `true` {
+		tokenMap := make(map[string]string)
+		item.Content = services.HandleReportContent(item.Content, "add", tokenMap)
+	}
+
 	classifyNameMap := make(map[int]*models.EnglishClassifyFullName)
 	if item.ClassifyIdSecond > 0 {
 		nameList, tErr := models.GetEnglishClassifyFullNameByIds([]int{item.ClassifyIdSecond})
@@ -682,8 +696,9 @@ func (this *EnglishReportController) PublishReport() {
 			}()
 
 			// 生成报告pdf和长图
-			if req.ReportUrl != "" {
-				go services.Report2pdfAndJpeg(req.ReportUrl, report.Id, 2)
+			pdfUrl := services.GetGeneralEnglishReportPdfUrl(report.Id, report.ReportCode)
+			if pdfUrl != "" {
+				go services.Report2pdfAndJpeg(pdfUrl, report.Id, 2)
 			}
 		} else {
 			// 从无审批切换为有审批, 状态重置
@@ -785,8 +800,9 @@ func (this *EnglishReportController) PrePublishReport() {
 	}
 
 	// 生成报告pdf和长图
-	if req.ReportUrl != "" {
-		go services.Report2pdfAndJpeg(req.ReportUrl, report.Id, 2)
+	pdfUrl := services.GetGeneralEnglishReportPdfUrl(report.Id, report.ReportCode)
+	if pdfUrl != "" {
+		go services.Report2pdfAndJpeg(pdfUrl, report.Id, 2)
 	}
 
 	br.Ret = 200
@@ -945,6 +961,7 @@ func (this *EnglishReportController) SaveReportContent() {
 	}
 
 	if noChangeFlag != 1 {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		content := req.Content
 		if content == "" {
 			content = this.GetString("Content")
@@ -1020,6 +1037,18 @@ func (this *EnglishReportController) ClassifyIdDetail() {
 		item.Content = html.UnescapeString(item.Content)
 		item.ContentSub = html.UnescapeString(item.ContentSub)
 
+		businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取配置失败,Err:" + err.Error()
+			return
+		}
+
+		if businessConf.ConfVal == `true` {
+			tokenMap := make(map[string]string)
+			item.Content = services.HandleReportContent(item.Content, "add", tokenMap)
+		}
+
 		classifyNameMap := make(map[int]*models.EnglishClassifyFullName)
 		if item.ClassifyIdSecond > 0 {
 			nameList, tErr := models.GetEnglishClassifyFullNameByIds([]int{item.ClassifyIdSecond})
@@ -1421,3 +1450,63 @@ func (this *EnglishReportController) CancelApprove() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// @Title 获取报告分享链接
+// @Description 获取报告分享链接
+// @Param   ReportId   query   int  true       "报告id"
+// @Success 200 {object} models.EnglishReportDetailView
+// @router /share_url [get]
+func (this *EnglishReportController) GetShareUrl() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	/*var req models.ReportDetailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}*/
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取参数失败!"
+		br.ErrMsg = "获取参数失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	item, err := models.GetEnglishReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	token, err := services.GetEnglishReportToken(reportId, item.ReportCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = token
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 6 - 2
controllers/material/material.go

@@ -753,9 +753,13 @@ func (this *MaterialController) List() {
 	if keyword != "" {
 		switch this.Lang {
 		case utils.LANG_EN:
-			condition += ` AND  ( material_name_en LIKE '%` + keyword + `%' )`
+			likeKey := `%` + keyword + `%`
+			condition += ` AND  ( material_name_en LIKE ? )`
+			pars = append(pars, likeKey)
 		default:
-			condition += ` AND  ( material_name LIKE '%` + keyword + `%' )`
+			likeKey := `%` + keyword + `%`
+			condition += ` AND  ( material_name LIKE ? )`
+			pars = append(pars, likeKey)
 		}
 	}
 

+ 5 - 1
controllers/ppt.go

@@ -53,7 +53,11 @@ func (this *PptController) ListPpt() {
 	var pars []interface{}
 
 	if keyWord != "" {
-		condition += ` AND (title LIKE '%` + keyWord + `%' OR admin_real_name LIKE '%` + keyWord + `%' ) `
+		//condition += ` AND (title LIKE '%` + keyWord + `%' OR admin_real_name LIKE '%` + keyWord + `%' ) `
+		likeKey := `%` + keyWord + `%`
+		condition += ` AND (title LIKE ? OR admin_real_name LIKE ? ) `
+		pars = append(pars, likeKey)
+		pars = append(pars, likeKey)
 	}
 	total, err := models.GetPptListCount(condition, pars)
 	if err != nil {

+ 81 - 3
controllers/report.go

@@ -8,9 +8,6 @@ import (
 	"eta/eta_mobile/services/data"
 	"eta/eta_mobile/utils"
 	"fmt"
-	"github.com/beego/beego/v2/server/web"
-	"github.com/h2non/filetype"
-	"github.com/tealeg/xlsx"
 	"html"
 	"io"
 	"os"
@@ -19,6 +16,10 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/server/web"
+	"github.com/h2non/filetype"
+	"github.com/tealeg/xlsx"
 )
 
 // ReportController 报告
@@ -1986,3 +1987,80 @@ func (this *ReportController) CheckDayWeekReportChapterVideo() {
 	br.Msg = "保存成功"
 	br.Data = typeNameArr
 }
+
+// ShareGenerate
+// @Title 获取复制链接
+// @Description 获取复制链接
+// @Param	request	body models.ReportShartLinkReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /share/generate [post]
+func (this *ReportController) ShareGenerate() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req models.ReportShartUrlReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	var title string
+	reportItem, _ := models.GetReportByReportId(req.ReportId)
+	if reportItem != nil && reportItem.Title != "" {
+		title = reportItem.Title
+	}
+
+	link, err := services.GetReportShareUrlToken(req, this.SysUser.AdminId)
+	if err != nil || link == "" {
+		br.Msg = "复制链接失败"
+		br.ErrMsg = "获取复制链接失败, Err: " + err.Error()
+		return
+	}
+
+	resp := new(models.ReportShartUrlResp)
+	resp.UrlToken = fmt.Sprint(link, " ", title)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ShareTransform
+// @Title 获取原始链接
+// @Description 获取原始链接
+// @Param	Token   query   string  true    "复制链接的token"
+// @Success 200 Ret=200 操作成功
+// @router /share/link [get]
+func (this *ReportCommonController) ShareTransform() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	token := this.GetString("Token")
+	tokenArr := strings.Split(token, " ")
+	if len(tokenArr) > 0 {
+		token = tokenArr[0]
+	}
+
+	link, msg, err := services.TransfromToOriginUrl(token)
+	if err != nil {
+		if msg == "" {
+			msg = "获取失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = "获取复制链接失败, Err: " + err.Error()
+		return
+	}
+
+	this.Ctx.Redirect(302, link)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 21 - 2
controllers/report_chapter.go

@@ -363,6 +363,7 @@ func (this *ReportController) EditDayWeekChapter() {
 	// 更新章节及指标
 	contentSub := ""
 	if req.Content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
@@ -405,6 +406,10 @@ func (this *ReportController) EditDayWeekChapter() {
 	reportChapterInfo.LastModifyAdminId = sysUser.AdminId
 	reportChapterInfo.LastModifyAdminName = sysUser.RealName
 	reportChapterInfo.ContentModifyTime = time.Now()
+
+	if req.ContentStruct != `` {
+		req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+	}
 	reportChapterInfo.ContentStruct = html.EscapeString(req.ContentStruct)
 
 	updateCols = append(updateCols, "Author", "Content", "ContentSub", "IsEdit", "ModifyTime")
@@ -776,6 +781,19 @@ func (this *ReportController) GetDayWeekChapter() {
 	chapterItem.ContentSub = html.UnescapeString(chapterItem.ContentSub)
 	chapterItem.ContentStruct = html.UnescapeString(chapterItem.ContentStruct)
 
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+
+	if businessConf.ConfVal == `true` {
+		tokenMap := make(map[string]string)
+		chapterItem.Content = services.HandleReportContent(chapterItem.Content, "add", tokenMap)
+		chapterItem.ContentStruct = services.HandleReportContentStruct(chapterItem.ContentStruct, "add", tokenMap)
+	}
+
 	// 授权用户列表map
 	chapterGrantIdList := make([]int, 0)
 	// 关联品种id列表map
@@ -814,6 +832,7 @@ func (this *ReportController) GetDayWeekChapter() {
 		ReportChapterItem: *chapterItem,
 		GrandAdminIdList:  chapterGrantIdList,
 		PermissionIdList:  chapterPermissionIdList,
+		FreeReportRatio:   reportInfo.FreeReportRatio,
 	}
 
 	// 获取当前编辑状态
@@ -1341,7 +1360,7 @@ func (this *ReportController) PublishDayWeekReportChapter() {
 
 	// 更新章节ES
 	{
-		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId, reportInfo.IsPublicPublish)
 	}
 
 	// 同时发布报告
@@ -1624,7 +1643,7 @@ func (this *ReportController) CancelPublishReportChapter() {
 
 	// 更新章节ES
 	{
-		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId, reportInfo.IsPublicPublish)
 	}
 
 	br.Ret = 200

+ 101 - 21
controllers/report_v2.go

@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"encoding/json"
+	"eta/eta_mobile/cache"
 	"eta/eta_mobile/models"
 	"eta/eta_mobile/models/report"
 	"eta/eta_mobile/models/report_approve"
@@ -391,19 +392,19 @@ func (this *ReportController) Add() {
 
 	var contentSub string
 	if req.Content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
 			br.ErrMsg = "存在非法标签, Err: " + e.Error()
 			return
 		}
-		content, e := services.FilterReportContentBr(req.Content)
+		req.Content, e = services.FilterReportContentBr(req.Content)
 		if e != nil {
 			br.Msg = "内容去除前后空格失败"
 			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
 			return
 		}
-		req.Content = content
 
 		contentSub, err = services.GetReportContentSub(req.Content)
 		if err != nil {
@@ -412,6 +413,10 @@ func (this *ReportController) Add() {
 		}
 	}
 
+	if req.ContentStruct != `` {
+		req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+	}
+
 	// 报告期数
 	maxStage, err := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
 	if err != nil {
@@ -470,6 +475,7 @@ func (this *ReportController) Add() {
 
 	item.ClassifyIdThird = req.ClassifyIdThird
 	item.ClassifyNameThird = classifyMap[req.ClassifyIdThird]
+	item.FreeReportRatio = req.FreeReportRatio
 
 	// 产品要求,如果是多人协作,那么就是章节类型的报告
 	if req.CollaborateType == 2 {
@@ -490,6 +496,7 @@ func (this *ReportController) Add() {
 	item.ReportLayout = req.ReportLayout
 	item.IsPublicPublish = req.IsPublicPublish
 	item.ReportCreateTime = time.Now()
+	item.MiniShow = req.MiniShow
 
 	reportDate := time.Now()
 	t, _ := time.ParseInLocation(utils.FormatDate, req.CreateTime, time.Local)
@@ -587,6 +594,8 @@ func (this *ReportController) Edit() {
 		return
 	}
 
+	req.Content = services.HandleReportContent(req.Content, "del", nil)
+	req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
 	// 编辑报告信息
 	err, errMsg := services.EditReport(reportInfo, req, sysUser)
 	if err != nil {
@@ -652,6 +661,7 @@ func (this *ReportController) Detail() {
 		return
 	}
 	chapterList := make([]*models.ReportChapter, 0)
+	pageList := make([]*report.ContentPage, 0)
 	if item.HasChapter == 1 {
 		// 获取章节内容
 		tmpChapterList, err := models.GetPublishedChapterListByReportId(item.Id)
@@ -669,38 +679,99 @@ func (this *ReportController) Detail() {
 				chapterList = append(chapterList, item)
 			}
 		}
-
+		if item.ReportLayout == 3 {
+			var chapterMap = make(map[int]bool)
+			for _, chapter := range tmpChapterList {
+				chapterMap[chapter.ReportChapterId] = true
+			}
+			pages, err := report.GetFreeLayoutChapterPagesByReportId(item.Id)
+			if err != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取自由布局内容页失败, Err: " + err.Error()
+				return
+			}
+			for _, page := range pages {
+				if chapterMap[page.ReportChapterId] {
+					page.Content = html.UnescapeString(page.Content)
+					page.ContentStruct = html.UnescapeString(page.ContentStruct)
+					pageList = append(pageList, page)
+				}
+			}
+		}
 		//item.Abstract = item.Title
+	} else {
+		if item.ReportLayout == 3 {
+			pages, err := report.GetFreeLayoutPagesByReportId(item.Id)
+			if err != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取自由布局内容页失败, Err: " + err.Error()
+				return
+			}
+			for _, page := range pages {
+				page.Content = html.UnescapeString(page.Content)
+				page.ContentStruct = html.UnescapeString(page.ContentStruct)
+				pageList = append(pageList, page)
+			}
+		}
 	}
+
 	item.Content = html.UnescapeString(item.Content)
 	item.ContentSub = html.UnescapeString(item.ContentSub)
 	item.ContentStruct = html.UnescapeString(item.ContentStruct)
 
 	if item.HeadResourceId > 0 {
 		headResource, err := smart_report.GetResourceItemById(item.HeadResourceId)
-		if err != nil {
+		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "操作失败"
 			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
 			return
 		}
-		item.HeadImg = headResource.ImgUrl
-		item.HeadStyle = headResource.Style
+		if headResource != nil && headResource.ResourceId > 0 {
+			item.HeadImg = headResource.ImgUrl
+			item.HeadStyle = headResource.Style
+		}
 	}
 
 	if item.EndResourceId > 0 {
 		endResource, err := smart_report.GetResourceItemById(item.EndResourceId)
-		if err != nil {
+		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "操作失败"
-			br.ErrMsg = "获取资源库版失败, Err: " + err.Error()
+			br.ErrMsg = "获取资源库版失败, Err: " + err.Error()
 			return
 		}
-		item.EndImg = endResource.ImgUrl
-		item.EndStyle = endResource.Style
+		if endResource != nil && endResource.ResourceId > 0 {
+			item.EndImg = endResource.ImgUrl
+			item.EndStyle = endResource.Style
+		}
+	}
+
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+
+	if businessConf.ConfVal == `true` {
+		tokenMap := make(map[string]string)
+		item.Content = services.HandleReportContent(item.Content, "add", tokenMap)
+		item.ContentStruct = services.HandleReportContentStruct(item.ContentStruct, "add", tokenMap)
+		for _, v := range chapterList {
+			v.Content = services.HandleReportContent(v.Content, "add", tokenMap)
+			v.ContentStruct = services.HandleReportContentStruct(v.ContentStruct, "add", tokenMap)
+		}
+		if item.ReportLayout == 3 {
+			for _, page := range pageList {
+				page.Content = services.HandleReportContent(page.Content, "add", tokenMap)
+				page.ContentStruct = services.HandleReportContentStruct(page.ContentStruct, "add", tokenMap)
+			}
+		}
 	}
 
 	resp := &models.ReportDetailView{
-		ReportDetail: item,
-		ChapterList:  chapterList,
+		ReportDetail:           item,
+		ChapterList:            chapterList,
+		FreeLayoutContentPages: pageList,
 	}
 	br.Ret = 200
 	br.Success = true
@@ -771,31 +842,33 @@ func (this *ReportController) SaveReportContent() {
 
 	// 内容有过修改的话,那么逻辑处理
 	if noChangeFlag != 1 {
-		content := req.Content
-		if content == "" {
-			content = this.GetString("Content")
+		if req.Content == "" {
+			req.Content = this.GetString("Content")
 		}
-		if content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
+
+		if req.Content != "" {
 			e := utils.ContentXssCheck(req.Content)
 			if e != nil {
 				br.Msg = "存在非法标签"
 				br.ErrMsg = "存在非法标签, Err: " + e.Error()
 				return
 			}
-			contentClean, e := services.FilterReportContentBr(req.Content)
+			req.Content, e = services.FilterReportContentBr(req.Content)
 			if e != nil {
 				br.Msg = "内容去除前后空格失败"
 				br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
 				return
 			}
-			content = contentClean
 
-			contentSub, err := services.GetReportContentSub(content)
+			req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+
+			contentSub, err := services.GetReportContentSub(req.Content)
 			if err != nil {
 				go alarm_msg.SendAlarmMsg("解析 ContentSub 失败,Err:"+err.Error(), 3)
 				//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
 			}
-			reportInfo.Content = html.EscapeString(content)
+			reportInfo.Content = html.EscapeString(req.Content)
 			reportInfo.ContentSub = html.EscapeString(contentSub)
 			reportInfo.ContentStruct = html.EscapeString(req.ContentStruct)
 			reportInfo.HeadImg = req.HeadImg
@@ -1332,6 +1405,10 @@ func (this *ReportController) PublishCancelReport() {
 	go func() {
 		_, _ = models.AddReportStateRecord(recordItem)
 	}()
+
+	// 报告取消发布成功后,需要将相关信息入知识库
+	go cache.RagEtaReportOpToCache(reportInfo.Id, 0, `un_publish`)
+
 	br.Ret = 200
 	br.Success = true
 }
@@ -1443,7 +1520,7 @@ func (this *ReportController) PrePublishReport() {
 
 	// 生成报告pdf和长图
 	{
-		reportPdfUrl := services.GetGeneralPdfUrl(reportDetail.ReportCode, reportDetail.ReportLayout)
+		reportPdfUrl := services.GetGeneralPdfUrl(reportDetail.Id, reportDetail.ReportCode, reportDetail.ReportLayout)
 		go services.Report2pdfAndJpeg(reportPdfUrl, reportDetail.Id, 1)
 	}
 
@@ -1660,6 +1737,9 @@ func (this *ReportController) CancelApprove() {
 	//	return
 	//}
 
+	// 报告发布成功后,需要将相关信息入知识库
+	go cache.RagEtaReportOpToCache(reportItem.Id, 0, `un_publish`)
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"

+ 5 - 1
controllers/sandbox/sandbox.go

@@ -54,7 +54,11 @@ func (this *SandboxController) ListByQuote() {
 	}
 
 	if keyword != "" {
-		condition += ` AND  ( a.name LIKE '%` + keyword + `%' OR  a.chart_permission_name LIKE '%` + keyword + `%' )`
+		//condition += ` AND  ( a.name LIKE '%` + keyword + `%' OR  a.chart_permission_name LIKE '%` + keyword + `%' )`
+		likeKey := `%` + keyword + `%`
+		condition += ` AND  ( a.name LIKE ? OR  a.chart_permission_name LIKE ? )`
+		pars = append(pars, likeKey)
+		pars = append(pars, likeKey)
 	}
 
 	//获取指标信息

+ 11 - 7
controllers/smart_report/smart_report.go

@@ -391,24 +391,28 @@ func (this *SmartReportController) Detail() {
 	resp := smart_report.FormatSmartReport2Item(item)
 	if resp.HeadResourceId > 0 {
 		headResource, err := smart_report.GetResourceItemById(resp.HeadResourceId)
-		if err != nil {
+		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "操作失败"
 			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
 			return
 		}
-		resp.HeadImg = headResource.ImgUrl
-		resp.HeadStyle = headResource.Style
+		if headResource != nil && headResource.ResourceId > 0 {
+			resp.HeadImg = headResource.ImgUrl
+			resp.HeadStyle = headResource.Style
+		}
 	}
 
 	if resp.EndResourceId > 0 {
 		endResource, err := smart_report.GetResourceItemById(resp.EndResourceId)
-		if err != nil {
+		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "操作失败"
-			br.ErrMsg = "获取资源库版失败, Err: " + err.Error()
+			br.ErrMsg = "获取资源库版失败, Err: " + err.Error()
 			return
 		}
-		resp.EndImg = endResource.ImgUrl
-		resp.EndStyle = endResource.Style
+		if endResource != nil && endResource.ResourceId > 0 {
+			resp.EndImg = endResource.ImgUrl
+			resp.EndStyle = endResource.Style
+		}
 	}
 	br.Ret = 200
 	br.Success = true

+ 4 - 3
go.mod

@@ -23,8 +23,10 @@ require (
 	github.com/go-xorm/xorm v0.7.9
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
 	github.com/gorilla/websocket v1.5.1
+	github.com/h2non/filetype v1.1.3
 	github.com/jung-kurt/gofpdf v1.16.2
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
+	github.com/microcosm-cc/bluemonday v1.0.27
 	github.com/minio/minio-go/v7 v7.0.69
 	github.com/mojocn/base64Captcha v1.3.6
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
@@ -33,6 +35,7 @@ require (
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
 	github.com/silenceper/wechat/v2 v2.1.6
+	github.com/spaolacci/murmur3 v1.1.0
 	github.com/tealeg/xlsx v1.0.5
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.897
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.897
@@ -40,6 +43,7 @@ require (
 	github.com/xuri/excelize/v2 v2.8.1
 	github.com/yidane/formula v0.0.0-20220322063702-c9da84ba3476
 	go.mongodb.org/mongo-driver v1.15.0
+	golang.org/x/net v0.26.0
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )
 
@@ -75,7 +79,6 @@ require (
 	github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/gorilla/css v1.0.1 // indirect
-	github.com/h2non/filetype v1.1.3 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
@@ -85,7 +88,6 @@ require (
 	github.com/leodido/go-urn v1.2.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
-	github.com/microcosm-cc/bluemonday v1.0.27 // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
 	github.com/minio/sha256-simd v1.0.1 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -117,7 +119,6 @@ require (
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 	golang.org/x/crypto v0.24.0 // indirect
 	golang.org/x/image v0.14.0 // indirect
-	golang.org/x/net v0.26.0 // indirect
 	golang.org/x/sync v0.7.0 // indirect
 	golang.org/x/sys v0.21.0 // indirect
 	golang.org/x/text v0.16.0 // indirect

+ 2 - 9
go.sum

@@ -412,6 +412,8 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
 github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
@@ -501,8 +503,6 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
-golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
 golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -553,8 +553,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
 golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -570,8 +568,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
-golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -607,8 +603,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
-golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -630,7 +624,6 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
 golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=

+ 81 - 0
models/ai_predict_model/ai_predict_model_data.go

@@ -0,0 +1,81 @@
+package data_manage
+
+import (
+	"database/sql"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	ModelDataSourceMonthly = 1 // 月度预测数据
+	ModelDataSourceDaily   = 2 // 日度预测数据
+)
+
+// AiPredictModelData AI预测模型标的数据
+type AiPredictModelData struct {
+	AiPredictModelDataId  int             `orm:"column(ai_predict_model_data_id);pk" gorm:"primaryKey"`
+	AiPredictModelIndexId int             `description:"标的ID"`
+	IndexCode             string          `description:"标的编码"`
+	DataTime              time.Time       `description:"数据日期"`
+	Value                 sql.NullFloat64 `description:"实际值"`
+	PredictValue          sql.NullFloat64 `description:"预测值"`
+	Direction             string          `description:"方向"`
+	DeviationRate         string          `description:"偏差率"`
+	CreateTime            time.Time       `description:"创建时间"`
+	ModifyTime            time.Time       `description:"修改时间"`
+	DataTimestamp         int64           `description:"数据日期时间戳"`
+	Source                int             `description:"来源:1-月度预测(默认);2-日度预测"`
+}
+
+func (m *AiPredictModelData) TableName() string {
+	return "ai_predict_model_data"
+}
+
+type AiPredictModelDataCols struct {
+	PrimaryId             string
+	AiPredictModelIndexId string
+	IndexCode             string
+	DataTime              string
+	Value                 string
+	PredictValue          string
+	Direction             string
+	DeviationRate         string
+	CreateTime            string
+	ModifyTime            string
+	DataTimestamp         string
+	Source                string
+}
+
+func (m *AiPredictModelData) Cols() AiPredictModelDataCols {
+	return AiPredictModelDataCols{
+		PrimaryId:             "ai_predict_model_data_id",
+		AiPredictModelIndexId: "ai_predict_model_index_id",
+		IndexCode:             "index_code",
+		DataTime:              "data_time",
+		Value:                 "value",
+		PredictValue:          "predict_value",
+		Direction:             "direction",
+		DeviationRate:         "deviation_rate",
+		CreateTime:            "create_time",
+		ModifyTime:            "modify_time",
+		DataTimestamp:         "data_timestamp",
+		Source:                "source",
+	}
+}
+
+func (m *AiPredictModelData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sqlRun := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sqlRun, pars...).QueryRows(&items)
+	return
+}

+ 130 - 0
models/ai_predict_model/ai_predict_model_framework.go

@@ -0,0 +1,130 @@
+package data_manage
+
+import (
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelFramework 图库框架表
+type AiPredictModelFramework struct {
+	AiPredictModelFrameworkId int       `orm:"column(ai_predict_model_framework_id);pk" gorm:"primaryKey"`
+	FrameworkCode             string    `description:"框架唯一编码"`
+	FrameworkName             string    `description:"框架名称"`
+	FrameworkImg              string    `description:"框架图片"`
+	FrameworkContent          string    `description:"框架内容"`
+	IsPublic                  int       `description:"是否公开:0-私有;1-公开"`
+	PublicTime                time.Time `description:"公开时间"`
+	Sort                      int       `description:"排序"`
+	AdminId                   int       `description:"创建人ID"`
+	AdminName                 string    `description:"创建人姓名"`
+	CreateTime                time.Time `description:"创建时间"`
+	ModifyTime                time.Time `description:"更新时间"`
+}
+
+func (m *AiPredictModelFramework) TableName() string {
+	return "ai_predict_model_framework"
+}
+
+func (m *AiPredictModelFramework) PrimaryId() string {
+	return AiPredictModelFrameworkColumns.AiPredictModelFrameworkId
+}
+
+var AiPredictModelFrameworkColumns = struct {
+	AiPredictModelFrameworkId string
+	FrameworkCode             string
+	FrameworkName             string
+	FrameworkImg              string
+	FrameworkContent          string
+	IsPublic                  string
+	PublicTime                string
+	Sort                      string
+	AdminId                   string
+	AdminName                 string
+	CreateTime                string
+	ModifyTime                string
+}{
+	AiPredictModelFrameworkId: "ai_predict_model_framework_id",
+	FrameworkCode:             "framework_code",
+	FrameworkName:             "framework_name",
+	FrameworkImg:              "framework_img",
+	FrameworkContent:          "framework_content",
+	IsPublic:                  "is_public",
+	PublicTime:                "public_time",
+	Sort:                      "sort",
+	AdminId:                   "admin_id",
+	AdminName:                 "admin_name",
+	CreateTime:                "create_time",
+	ModifyTime:                "modify_time",
+}
+
+func (m *AiPredictModelFramework) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+// AiPredictModelFrameworkItem 图库框架表信息
+type AiPredictModelFrameworkItem struct {
+	AiPredictModelFrameworkId 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                     []*AiPredictModelFrameworkNodeItem `description:"框架节点" gorm:"-"`
+	Button                    AiPredictModelFrameworkItemButton  `description:"操作按钮" gorm:"-"`
+}
+
+// AiPredictModelFrameworkItemButton 操作按钮
+type AiPredictModelFrameworkItemButton struct {
+	OpButton     bool `description:"是否可编辑"`
+	DeleteButton bool `description:"是否可删除"`
+	MoveButton   bool `description:"是否可移动"`
+}
+
+// FormatAiPredictModelFramework2Item 格式化框架信息
+func FormatAiPredictModelFramework2Item(origin *AiPredictModelFramework, nodes []*AiPredictModelFrameworkNodeItem) (item *AiPredictModelFrameworkItem) {
+	if origin == nil {
+		return
+	}
+	item = new(AiPredictModelFrameworkItem)
+	item.AiPredictModelFrameworkId = origin.AiPredictModelFrameworkId
+	item.FrameworkCode = origin.FrameworkCode
+	item.FrameworkName = origin.FrameworkName
+	item.FrameworkImg = origin.FrameworkImg
+	item.FrameworkContent = origin.FrameworkContent
+	item.IsPublic = origin.IsPublic
+	item.PublicTime = utils.TimeTransferString(utils.FormatDateTime, origin.PublicTime)
+	item.Sort = origin.Sort
+	item.AdminId = origin.AdminId
+	item.AdminName = origin.AdminName
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	item.Nodes = nodes
+	return
+}
+
+// AiPredictModelFrameworkPublicMenuItem 公开框架目录
+type AiPredictModelFrameworkPublicMenuItem struct {
+	AdminId    int                            `description:"创建人ID"`
+	MenuName   string                         `description:"目录名称"`
+	Frameworks []*AiPredictModelFrameworkItem `description:"框架列表"`
+}

+ 33 - 0
models/ai_predict_model/ai_predict_model_framework_node.go

@@ -0,0 +1,33 @@
+package data_manage
+
+import (
+	"time"
+)
+
+// AiPredictModelFrameworkNode 图库框架节点表
+type AiPredictModelFrameworkNode struct {
+	AiPredictModelFrameworkNodeId int       `orm:"column(ai_predict_model_framework_node_id);pk" gorm:"primaryKey"`
+	AiPredictModelFrameworkId     int       `description:"框架ID"`
+	FrameworkName                 string    `description:"框架名称"`
+	NodeId                        string    `description:"节点ID"`
+	NodeName                      string    `description:"节点名称"`
+	AiPredictModelIndexId         int       `description:"我的图表分类ID"`
+	CreateTime                    time.Time `description:"创建时间"`
+}
+
+func (m *AiPredictModelFrameworkNode) TableName() string {
+	return "ai_predict_model_framework_node"
+}
+
+// AiPredictModelFrameworkNodeItem 图库框架节点信息
+type AiPredictModelFrameworkNodeItem struct {
+	AiPredictModelFrameworkNodeId int
+	AiPredictModelFrameworkId     int    `description:"框架ID"`
+	FrameworkName                 string `description:"框架名称"`
+	NodeId                        string `description:"节点ID"`
+	NodeName                      string `description:"节点名称"`
+	AiPredictModelIndexId         int    `description:"模型标的ID"`
+	IndexName                     string `description:"模型标的名称"`
+	AiPredictModelNum             int    `description:"分类下的图表数"`
+	CreateTime                    string `description:"创建时间"`
+}

+ 96 - 0
models/ai_predict_model/ai_predict_model_index.go

@@ -0,0 +1,96 @@
+package data_manage
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// AiPredictModelIndex AI预测模型标的
+type AiPredictModelIndex struct {
+	AiPredictModelIndexId int       `orm:"column(ai_predict_model_index_id);pk" gorm:"primaryKey"`
+	IndexName             string    `description:"标的名称"`
+	IndexCode             string    `description:"自生成的指标编码"`
+	ClassifyId            int       `description:"分类ID"`
+	ModelFramework        string    `description:"模型框架"`
+	PredictDate           time.Time `description:"预测日期"`
+	PredictValue          float64   `description:"预测值"`
+	PredictFrequency      string    `description:"预测频度"`
+	DirectionAccuracy     string    `description:"方向准确度"`
+	AbsoluteDeviation     string    `description:"绝对偏差"`
+	ExtraConfig           string    `description:"模型参数"`
+	Sort                  int       `description:"排序"`
+	SysUserId             int       `description:"创建人ID"`
+	SysUserRealName       string    `description:"创建人姓名"`
+	LeftMin               string    `description:"图表左侧最小值"`
+	LeftMax               string    `description:"图表左侧最大值"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelIndex) TableName() string {
+	return "ai_predict_model_index"
+}
+
+type AiPredictModelIndexCols struct {
+	PrimaryId         string
+	IndexName         string
+	IndexCode         string
+	ClassifyId        string
+	ModelFramework    string
+	PredictDate       string
+	PredictValue      string
+	DirectionAccuracy string
+	AbsoluteDeviation string
+	ExtraConfig       string
+	Sort              string
+	SysUserId         string
+	SysUserRealName   string
+	LeftMin           string
+	LeftMax           string
+	CreateTime        string
+	ModifyTime        string
+}
+
+func (m *AiPredictModelIndex) Cols() AiPredictModelIndexCols {
+	return AiPredictModelIndexCols{
+		PrimaryId:         "ai_predict_model_index_id",
+		IndexName:         "index_name",
+		IndexCode:         "index_code",
+		ClassifyId:        "classify_id",
+		ModelFramework:    "model_framework",
+		PredictDate:       "predict_date",
+		PredictValue:      "predict_value",
+		DirectionAccuracy: "direction_accuracy",
+		AbsoluteDeviation: "absolute_deviation",
+		ExtraConfig:       "extra_config",
+		Sort:              "sort",
+		SysUserId:         "sys_user_id",
+		SysUserRealName:   "sys_user_real_name",
+		LeftMin:           "left_min",
+		LeftMax:           "left_max",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+	}
+}
+
+func (m *AiPredictModelIndex) GetItemById(id int) (item *AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+type AiPredictModelIndexExtraConfig struct {
+	MonthlyChart struct {
+		LeftMin string `description:"图表左侧最小值"`
+		LeftMax string `description:"图表左侧最大值"`
+		Unit    string `description:"单位"`
+	}
+	DailyChart struct {
+		LeftMin           string `description:"图表左侧最小值"`
+		LeftMax           string `description:"图表左侧最大值"`
+		Unit              string `description:"单位"`
+		PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
+	}
+}

+ 26 - 2
models/business_conf.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"html"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -51,14 +52,26 @@ const (
 	BusinessConfTencentApiSecretKey          = "TencentApiSecretKey"          // 腾讯云API-密钥对
 	BusinessConfTencentApiRecTaskCallbackUrl = "TencentApiRecTaskCallbackUrl" // 腾讯云API-语音识别回调地址
 	BusinessConfSmsJhgjVariable              = "SmsJhgjVariable"              // 聚合国际短信变量
-	BusinessConfEsIndexNameExcel             = "EsIndexNameExcel"             // ES索引名称-表格
-	BusinessConfEsIndexNameDataSource        = "EsIndexNameDataSource"        // ES索引名称-数据源
+
+	BusinessConfEdbStopRefreshRule = "EdbStopRefreshRule" // 是否停止指标刷新规则
+	BusinessConfReport2ImgUrl      = "Report2ImgUrl"      // 报告转长图地址(用于兼容内外网环境的)
+	BusinessConfReportViewUrl      = "ReportViewUrl"      // 报告详情地址     // 报告详情地址
+
+	// DongWu SMS configuration
+	BusinessConfDongwuSmsAppKey    = "DongWuSmsAppKey"    // 东吴短信AppKey
+	BusinessConfDongwuSmsApiUrl    = "DongWuSmsApiUrl"    // 东吴短信API地址
+
+	BusinessConfEsIndexNameExcel       = "EsIndexNameExcel"       // ES索引名称-表格
+	BusinessConfEsIndexNameDataSource  = "EsIndexNameDataSource"  // 聚合国际短信变量
+	BusinessConfIsOpenChartExpired     = "IsOpenChartExpired"     // 是否开启图表有效期鉴权/报告禁止复制
+	BusinessConfReportChartExpiredTime = "ReportChartExpiredTime" // 图表有效期鉴权时间,单位:分钟
 )
 
 const (
 	BusinessConfReportApproveTypeEta   = "eta"
 	BusinessConfReportApproveTypeOther = "other"
 	BusinessConfClientFlagNanHua       = "nhqh" // 南华标记
+	BusinessConfClientFlagDongwu       = "dwqh" // 东吴标记
 	BusinessConfEmailClientSmtp        = "smtp" // 普通邮箱标记
 )
 
@@ -255,4 +268,15 @@ func InitBusinessConf() {
 	if BusinessConfMap[BusinessConfEsIndexNameDataSource] != "" {
 		utils.EsDataSourceIndexName = BusinessConfMap[BusinessConfEsIndexNameDataSource]
 	}
+
+	// 图表有效期的过期时间
+	if BusinessConfMap[BusinessConfReportChartExpiredTime] != "" {
+		reportChartExpiredTime, _ := strconv.Atoi(BusinessConfMap[BusinessConfReportChartExpiredTime])
+		if reportChartExpiredTime <= 0 {
+			reportChartExpiredTime = 30
+		}
+		utils.BusinessConfReportChartExpiredTime = time.Duration(reportChartExpiredTime) * time.Minute
+	} else {
+		utils.BusinessConfReportChartExpiredTime = 30 * time.Minute
+	}
 }

+ 81 - 16
models/data_manage/chart_classify.go

@@ -22,6 +22,10 @@ type ChartClassify struct {
 	Source              int       `description:"1:ETA图库;2:商品价格曲线"`
 	IsJoinPermission    int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	ChartClassifyNameEn string    `description:"英文分类名称"`
+	RootId              int       `description:"顶级分类id"`
+	IsSelected          int       `description:"是否精选资源,0:否;1:是"`
+	ResourceStatus      int       `description:"在ETA投研资源库中的状态,0:初始状态,1上架,2下架"`
+	LevelPath           string    `description:"所有的父级分类id"`
 }
 
 func AddChartClassify(item *ChartClassify) (lastId int64, err error) {
@@ -167,6 +171,12 @@ func GetChartClassifyByParentId(parentId, source int) (items []*ChartClassifyIte
 	return
 }
 
+func GetChartClassifyByParentIdAndIsSelected(parentId, source int, isSelected int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE parent_id=? AND source = ? AND is_selected = ? order by sort asc,chart_classify_id asc`
+	_, err = o.Raw(sql, parentId, source, isSelected).QueryRows(&items)
+	return
+}
 // GetChartClassifyAll
 // @param source int 1:ETA图库;2:商品价格曲线;3:相关性图表
 func GetChartClassifyAll(source int) (items []*ChartClassifyItems, err error) {
@@ -176,30 +186,41 @@ func GetChartClassifyAll(source int) (items []*ChartClassifyItems, err error) {
 	return
 }
 
+// GetChartClassifyAll
+// @param source int 1:ETA图库;2:商品价格曲线;3:相关性图表
+func GetChartClassifyIsSelectedAll(source int, isSelected int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE parent_id<>0 AND source = ? AND is_selected = ? order by sort asc,chart_classify_id asc`
+	_, err = o.Raw(sql, source, isSelected).QueryRows(&items)
+	return
+}
 type ChartClassifyItems struct {
 	ChartClassifyId     int `description:"分类id"`
 	ChartInfoId         int `description:"指标id"`
 	ChartClassifyName   string
 	ChartClassifyNameEn string
 	ParentId            int
-	Level               int    `description:"层级"`
-	Sort                int    `description:"排序字段,越小越靠前,默认值:10"`
-	UniqueCode          string `description:"唯一编码"`
-	Source              int    `description:"来源id"`
-	SourceName          string `description:"来源名称"`
-	SysUserId           int    `description:"创建人id"`
-	SysUserRealName     string `description:"创建人姓名"`
-	DateType            int    `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"`
-	StartDate           string `description:"自定义开始日期"`
-	EndDate             string `description:"自定义结束日期"`
-	ChartType           int    `description:"生成样式:1:曲线图,2:季节性图"`
-	Calendar            string `description:"公历/农历"`
-	SeasonStartDate     string `description:"季节性图开始日期"`
-	SeasonEndDate       string `description:"季节性图开始日期"`
-	Children            []*ChartClassifyItems
-	Button              ChartClassifyItemsButton `description:"按钮权限"`
+	Level               int                      `description:"层级"`
+	Sort                int                      `description:"排序字段,越小越靠前,默认值:10"`
+	UniqueCode          string                   `description:"唯一编码"`
+	Source              int                      `description:"来源id"`
+	SourceName          string                   `description:"来源名称"`
+	SysUserId           int                      `description:"创建人id"`
+	SysUserRealName     string                   `description:"创建人姓名"`
+	DateType            int                      `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"`
+	StartDate           string                   `description:"自定义开始日期"`
+	EndDate             string                   `description:"自定义结束日期"`
+	ChartType           int                      `description:"生成样式:1:曲线图,2:季节性图"`
+	Calendar            string                   `description:"公历/农历"`
+	SeasonStartDate     string                   `description:"季节性图开始日期"`
+	SeasonEndDate       string                   `description:"季节性图开始日期"`
+	Children            []*ChartClassifyItems    `gorm:"-"`
+	Button              ChartClassifyItemsButton `gorm:"-" description:"按钮权限"`
 	IsJoinPermission    int                      `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth       bool                     `description:"是否有数据权限,默认:false"`
+	Disable             bool                     `description:"勾选是否禁用"`
+	IsSelected          int                      `description:"是否精选资源,0:否;1:是"`
+	ResourceStatus      int                      `description:"在ETA投研资源库中的状态,0:初始状态,1上架,2下架"`
 }
 
 // ChartClassifyItemsButton 操作按钮
@@ -433,6 +454,14 @@ func GetChartClassifyAllBySource(source int) (items []*ChartClassifyItems, err e
 	return
 }
 
+// GetChartClassifyAllBySourceIsSelected 根据来源获取所有精选分类
+func GetChartClassifyAllBySourceIsSelected(source int, isSelected int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE source = ? AND is_selected = ? ORDER BY parent_id ASC, sort ASC, chart_classify_id ASC`
+	_, err = o.Raw(sql, source, isSelected).QueryRows(&items)
+	return
+}
+
 // GetChartInfoBySourceAndParentId 根据图表来源及父级ID获取图表
 func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*ChartClassifyItems, err error) {
 	o := orm.NewOrmUsingDB("data")
@@ -449,3 +478,39 @@ func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*Ch
 	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
+// GetChartClassifyInfoSelectedBySource 获取所有的精选目录
+func GetChartClassifyInfoSelectedBySource(source int) (items []*ChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE source = ? AND is_selected = ? ORDER BY parent_id ASC, sort ASC, chart_classify_id ASC`
+	_, err = o.Raw(sql, source, utils.ChartClassifyIsSelected).QueryRows(&items)
+	return
+}
+
+func GetChartClassifyByLevelPath(levelPath string, source int) (items []*ChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_classify where level_path like '` + levelPath + `%' and source = ?`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+
+func UpdateChartClassifyIsSelected(source int, isSelected int, levelPath string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE chart_classify SET is_selected = ? WHERE source = ? AND level_path LIKE '` + levelPath + `%'`
+	_, err = o.Raw(sql, isSelected, source).Exec()
+	return
+}
+
+func UpdateChartClassifyResourceStatus(source int, resourceStatus int, levelPath string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE chart_classify SET resource_status = ? WHERE source = ? AND level_path LIKE '` + levelPath + `%'`
+	_, err = o.Raw(sql, resourceStatus, source).Exec()
+	return
+}
+
+// 查询存在已经上架的图表的分类
+func GetChartClassifyHasUpChartBySource(source int, levelPath string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT count(1) FROM chart_info WHERE source = ? AND resource_status = ? and chart_classify_id in (SELECT chart_classify_id FROM chart_classify WHERE level_path like '` + levelPath + `%')`
+	err = o.Raw(sql, source, utils.ChartClassifyResourceStatusUp).QueryRow(&count)
+	return
+}

+ 6 - 0
models/data_manage/chart_edb_mapping.go

@@ -331,3 +331,9 @@ func GetChartMappingList(chartInfoId int) (list []*ChartEdbMapping, err error) {
 	_, err = o.Raw(sql, chartInfoId).QueryRows(&list)
 	return
 }
+
+func GetChartEdbMappingsByChartInfoId(chartInfoId int) (list []*ChartEdbInfoMapping, err error) {
+	sql := ` SELECT * FROM chart_edb_mapping AS a WHERE chart_info_id = ? ORDER BY chart_edb_mapping_id ASC`
+	_, err = orm.NewOrmUsingDB("data").Raw(sql, chartInfoId).QueryRows(&list)
+	return
+}

+ 10 - 0
models/data_manage/chart_info.go

@@ -57,6 +57,7 @@ type ChartInfo struct {
 	Unit              string `description:"中文单位名称"`
 	UnitEn            string `description:"英文单位名称"`
 	IsJoinPermission  int    `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	ForumChartInfoId  int    `description:"投研资源库图表ID"`
 }
 
 type ChartInfoMore struct {
@@ -2766,3 +2767,12 @@ type ExcelChartEdbView struct {
 	DataSequenceStr string `description:"数据序列选区"`
 	FromTag         string `description:"标签"`
 }
+
+// GetAiPredictChartInfoByIndexId 获取AI预测模型图表
+func GetAiPredictChartInfoByIndexId(source, indexId int) (item *ChartInfo, err error) {
+	sql := `SELECT * FROM chart_info WHERE chart_info_id = (
+		  SELECT chart_info_id FROM chart_edb_mapping WHERE source = ? AND edb_info_id = ?
+		) LIMIT 1`
+	err = orm.NewOrmUsingDB("data").Raw(sql, source, indexId).QueryRow(&item)
+	return
+}

+ 1 - 1
models/data_manage/data_manage_permission/classify_no_auth_record.go

@@ -10,7 +10,7 @@ import (
 // @Description: 资产分类数据权限未授权记录表
 type DataPermissionClassifyNoAuthRecord struct {
 	DataPermissionClassifyNoAuthRecordId int64     `json:"data_permission_classify_no_auth_record_id" orm:"column(data_permission_classify_no_auth_record_id);pk"` // 资产分类数据操作记录id
-	Source                               int32     `json:"source"`                                                                                                 // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                               int32     `json:"source"`                                                                                                 // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                            int32     `json:"sub_source"`                                                                                             // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode                         string    `json:"op_unique_code"`                                                                                         // 操作的唯一编码,主要是记录统一操作的日志
 	ClassifyId                           string    `json:"classify_id"`                                                                                            // 资产分类id(指标、图表、表格)

+ 1 - 1
models/data_manage/data_manage_permission/move.go

@@ -55,7 +55,7 @@ func ModifyDataUserIdByOldUserId(oldUserIdList []int, userId int, userName strin
 		}
 	}()
 
-	// 钢联化工数据库
+	// 上海钢联数据库
 	if isMoveMysteelChemical {
 		sql := `UPDATE base_from_mysteel_chemical_index SET sys_user_id=?,sys_user_real_name=? WHERE sys_user_id in (` + utils.GetOrmInReplace(num) + `)  `
 		_, err = o.Raw(sql, userId, userName, oldUserIdList).Exec()

+ 1 - 1
models/data_manage/data_manage_permission/move_record.go

@@ -9,7 +9,7 @@ import (
 // @Description: 数据资产转移记录表
 type DataPermissionMoveRecord struct {
 	DataPermissionMoveRecordId int64     `json:"data_permission_move_record_id" orm:"column(data_permission_move_record_id);pk"` // 数据操作记录id
-	Source                     int32     `json:"source"`                                                                         // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                     int32     `json:"source"`                                                                         // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                  int32     `json:"sub_source"`                                                                     // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode               string    `json:"op_unique_code"`                                                                 // 操作的唯一编码,主要是记录统一操作的日志
 	DataId                     string    `json:"data_id"`                                                                        // 资产id(指标、图表、表格)

+ 1 - 1
models/data_manage/data_manage_permission/no_auth_record.go

@@ -10,7 +10,7 @@ import (
 // @Description: 资产数据权限设置记录表
 type DataPermissionNoAuthRecord struct {
 	DataPermissionNoAuthRecordId int64     `json:"data_permission_no_auth_record_id" orm:"column(data_permission_no_auth_record_id);pk"` // 资产数据操作记录id
-	Source                       int32     `json:"source"`                                                                               // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                       int32     `json:"source"`                                                                               // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                    int32     `json:"sub_source"`                                                                           // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode                 string    `json:"op_unique_code"`                                                                       // 操作的唯一编码,主要是记录统一操作的日志
 	DataId                       string    `json:"data_id"`                                                                              // 资产id(指标、图表、表格)

+ 1 - 1
models/data_manage/edb_data_mysteel_chemical.go

@@ -4,7 +4,7 @@ import (
 	"github.com/beego/beego/v2/client/orm"
 )
 
-// GetEdbDataMysteelChemicalMaxOrMinDate 根据钢联化工指标code获取最大、最小日期
+// GetEdbDataMysteelChemicalMaxOrMinDate 根据上海钢联指标code获取最大、最小日期
 func GetEdbDataMysteelChemicalMaxOrMinDate(edbCode string) (minDate, maxDate string, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT MIN(data_time) AS minDate,MAX(data_time) AS maxDate FROM edb_data_mysteel_chemical WHERE edb_code=? `

+ 22 - 7
models/data_manage/edb_info.go

@@ -137,6 +137,9 @@ type EdbInfoSearch struct {
 type StockInfo struct {
 	StockCode string
 	EdbCode   string
+	Days      string `description:"日期序列-日期类型"`
+	Period    string `description:"日期序列-周期"`
+	PriceAdj  string `description:"日期序列-复权方式"`
 	DataList  []*EdbInfoSearchData
 }
 
@@ -1519,13 +1522,17 @@ type BatchAddEdbInfoReq struct {
 }
 
 type BatchAddEdbInfo struct {
-	Source     int    `description:"来源id"`
-	EdbName    string `description:"指标名称"`
-	Frequency  string `description:"频率"`
-	Unit       string `description:"单位"`
-	ClassifyId int    `description:"分类id"`
-	StockCode  string `description:"证券代码"`
-	EdbCode    string `description:"指标编码"`
+	Source       int    `description:"来源id"`
+	EdbName      string `description:"指标名称"`
+	Frequency    string `description:"频率"`
+	Unit         string `description:"单位"`
+	ClassifyId   int    `description:"分类id"`
+	StockCode    string `description:"证券代码"`
+	EdbCode      string `description:"指标编码"`
+	Days         string `description:"日期序列-日期类型"`
+	Period       string `description:"日期序列-周期"`
+	PriceAdj     string `description:"日期序列-复权方式"`
+	ApiExtraPars string `description:"API额外参数"`
 }
 
 func GetEdbInfoWsdMaxAndMinInfo(source, subSource int, edbCode string) (item *EdbInfoMaxAndMinInfo, err error) {
@@ -1883,3 +1890,11 @@ func getThsHfAllDataByMongo(edbInfoId, source, subSource int, startDataTime stri
 
 	return
 }
+
+// EdbInfoExtra 指标额外数据-extra字段
+type EdbInfoExtra struct {
+	ApiExtraPars string `description:"API-额外参数(如同花顺日期序列)"`
+	Days         string `description:"日期序列-日期类型"`
+	Period       string `description:"日期序列-周期"`
+	PriceAdj     string `description:"日期序列-复权方式"`
+}

+ 3 - 3
models/data_manage/mysteel_chemical_classify.go

@@ -7,7 +7,7 @@ import (
 	"time"
 )
 
-// BaseFromMysteelChemicalClassify 钢联化工分类表
+// BaseFromMysteelChemicalClassify 上海钢联分类表
 type BaseFromMysteelChemicalClassify struct {
 	BaseFromMysteelChemicalClassifyId int       `orm:"column(base_from_mysteel_chemical_classify_id);pk"`
 	ClassifyName                      string    `description:"分类名称"`
@@ -20,7 +20,7 @@ type BaseFromMysteelChemicalClassify struct {
 	CreateTime                        time.Time `description:"创建时间"`
 }
 
-// AddBaseFromMysteelChemicalClassify 添加钢联化工分类
+// AddBaseFromMysteelChemicalClassify 添加上海钢联分类
 func AddBaseFromMysteelChemicalClassify(item *BaseFromMysteelChemicalClassify) (lastId int64, err error) {
 	o := orm.NewOrmUsingDB("data")
 	lastId, err = o.Insert(item)
@@ -43,7 +43,7 @@ func GetBaseFromMysteelChemicalClassifyById(classifyId int) (item *BaseFromMyste
 	return
 }
 
-// EditBaseFromMysteelChemicalClassify 修改钢联化工分类
+// EditBaseFromMysteelChemicalClassify 修改上海钢联分类
 func EditBaseFromMysteelChemicalClassify(classifyId int, classifyName string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `UPDATE base_from_mysteel_chemical_classify SET classify_name=?,modify_time=NOW() WHERE base_from_mysteel_chemical_classify_id=? `

+ 24 - 24
models/data_manage/mysteel_chemical_index.go

@@ -8,10 +8,10 @@ import (
 	"time"
 )
 
-// BaseFromMysteelChemicalIndex 钢联化工指标表
+// BaseFromMysteelChemicalIndex 上海钢联指标表
 type BaseFromMysteelChemicalIndex struct {
 	BaseFromMysteelChemicalIndexId    int       `orm:"column(base_from_mysteel_chemical_index_id);pk"`
-	BaseFromMysteelChemicalClassifyId int       `orm:"column(base_from_mysteel_chemical_classify_id)" description:"钢联化工指标分类id"`
+	BaseFromMysteelChemicalClassifyId int       `orm:"column(base_from_mysteel_chemical_classify_id)" description:"上海钢联指标分类id"`
 	IndexCode                         string    `description:"指标编码"`
 	IndexName                         string    `description:"指标名称"`
 	Unit                              string    `description:"单位"`
@@ -34,14 +34,14 @@ type BaseFromMysteelChemicalIndex struct {
 	EndValue                          float64   `description:"指标的最新值"`
 }
 
-// Update 更新钢联化工指标基础信息
+// Update 更新上海钢联指标基础信息
 func (item *BaseFromMysteelChemicalIndex) Update(cols []string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	_, err = o.Update(item, cols...)
 	return
 }
 
-// AddBaseFromMysteelChemicalIndex 添加钢联化工指标
+// AddBaseFromMysteelChemicalIndex 添加上海钢联指标
 func AddBaseFromMysteelChemicalIndex(item *BaseFromMysteelChemicalIndex) (lastId int64, err error) {
 	o := orm.NewOrmUsingDB("data")
 	lastId, err = o.Insert(item)
@@ -53,10 +53,10 @@ func AddBaseFromMysteelChemicalIndex(item *BaseFromMysteelChemicalIndex) (lastId
 	return
 }
 
-// BaseFromMysteelChemicalData 钢联化工指标数据表
+// BaseFromMysteelChemicalData 上海钢联指标数据表
 type BaseFromMysteelChemicalData struct {
 	BaseFromMysteelChemicalDataId  int       `orm:"column(base_from_mysteel_chemical_data_id);pk"`
-	BaseFromMysteelChemicalIndexId int       `orm:"column(base_from_mysteel_chemical_index_id)" description:"钢联化工指标id"`
+	BaseFromMysteelChemicalIndexId int       `orm:"column(base_from_mysteel_chemical_index_id)" description:"上海钢联指标id"`
 	IndexCode                      string    `description:"指标编码"`
 	DataTime                       time.Time `description:"数据日期"`
 	Value                          float64   `description:"数据值"`
@@ -64,7 +64,7 @@ type BaseFromMysteelChemicalData struct {
 	CreateTime                     time.Time `description:"创建时间"`
 }
 
-// MysteelChemicalFrequency 钢联化工频度
+// MysteelChemicalFrequency 上海钢联频度
 type MysteelChemicalFrequency struct {
 	Frequency string `description:"频度:1-日度 2-周度 3-月度 4-季度 5-年度 99-无固定频率"`
 }
@@ -79,7 +79,7 @@ func GetMysteelChemicalIndexByClassifyId(classifyId int) (items []*BaseFromMyste
 	return
 }
 
-// MysteelChemicalFrequencyByClassifyId 根据分类id获取钢联化工频度数据列表
+// MysteelChemicalFrequencyByClassifyId 根据分类id获取上海钢联频度数据列表
 func MysteelChemicalFrequencyByClassifyId(classifyId int) (items []*MysteelChemicalFrequency, err error) {
 	o := orm.NewOrmUsingDB("data")
 	if classifyId == 0 {
@@ -93,7 +93,7 @@ func MysteelChemicalFrequencyByClassifyId(classifyId int) (items []*MysteelChemi
 	}
 }
 
-// GetMysteelChemicalFrequency 获取钢联化工频度数据列表
+// GetMysteelChemicalFrequency 获取上海钢联频度数据列表
 func GetMysteelChemicalFrequency(condition string, pars []interface{}) (items []*MysteelChemicalFrequency, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT frequency FROM base_from_mysteel_chemical_index WHERE 1=1 AND frequency != "" `
@@ -105,11 +105,11 @@ func GetMysteelChemicalFrequency(condition string, pars []interface{}) (items []
 	return
 }
 
-// MysteelChemicalList 钢联化工指标列表
+// MysteelChemicalList 上海钢联指标列表
 type MysteelChemicalList struct {
 	Id                                int                `orm:"column(base_from_mysteel_chemical_index_id)"`
-	BaseFromMysteelChemicalClassifyId int                `orm:"column(base_from_mysteel_chemical_classify_id)" description:"钢联化工指标分类id"`
-	ParentClassifyId                  int                `description:"钢联化工指标父级分类id"`
+	BaseFromMysteelChemicalClassifyId int                `orm:"column(base_from_mysteel_chemical_classify_id)" description:"上海钢联指标分类id"`
+	ParentClassifyId                  int                `description:"上海钢联指标父级分类id"`
 	IndexCode                         string             `description:"指标编码"`
 	IndexName                         string             `description:"指标名称"`
 	UnitName                          string             `orm:"column(unit)"`
@@ -120,13 +120,13 @@ type MysteelChemicalList struct {
 	DataList                          []*MysteelChemicalData
 }
 
-// MysteelChemicalData 钢联化工数据列表
+// MysteelChemicalData 上海钢联数据列表
 type MysteelChemicalData struct {
 	InputValue string `orm:"column(value)" description:"值"`
 	DataTime   string `orm:"column(data_time)" description:"日期"`
 }
 
-// GetMysteelChemicalIndex 根据分类id获取钢联化工频度数据列表
+// GetMysteelChemicalIndex 根据分类id获取上海钢联频度数据列表
 func GetMysteelChemicalIndex(condition string, pars []interface{}) (items []*MysteelChemicalList, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM base_from_mysteel_chemical_index WHERE 1=1 `
@@ -139,7 +139,7 @@ func GetMysteelChemicalIndex(condition string, pars []interface{}) (items []*Mys
 	return
 }
 
-// GetMysteelChemicalIndexData 根据指标code获取钢联化工数据列表
+// GetMysteelChemicalIndexData 根据指标code获取上海钢联数据列表
 func GetMysteelChemicalIndexData(indexCode string, startSize, pageSize int) (items []*MysteelChemicalData, err error) {
 	sql := ` SELECT * FROM (
 	SELECT DISTINCT a.index_code,a.value,a.data_time FROM base_from_mysteel_chemical_data AS a WHERE index_code=? 
@@ -152,7 +152,7 @@ func GetMysteelChemicalIndexData(indexCode string, startSize, pageSize int) (ite
 	return
 }
 
-// GetMysteelChemicalIndexDataCount 根据指标code获取钢联化工数据列表 获取钢联数据总数
+// GetMysteelChemicalIndexDataCount 根据指标code获取上海钢联数据列表 获取钢联数据总数
 func GetMysteelChemicalIndexDataCount(indexCode string) (count int, err error) {
 	sql := `SELECT COUNT(1) AS count FROM (
 			SELECT * FROM (
@@ -224,7 +224,7 @@ func GetBaseFromMysteelChemicalDataMaxCount(classifyId int) (count int, err erro
 	return
 }
 
-// GetMysteelChemicalIndexDataByCode 通过钢联化工指标code获取所有数据列表
+// GetMysteelChemicalIndexDataByCode 通过上海钢联指标code获取所有数据列表
 func GetMysteelChemicalIndexDataByCode(indexCode string) (items []*MysteelChemicalData, err error) {
 	sql := ` SELECT * FROM (
 	SELECT DISTINCT a.index_code,a.value,a.data_time FROM base_from_mysteel_chemical_data AS a WHERE index_code=? 
@@ -237,7 +237,7 @@ func GetMysteelChemicalIndexDataByCode(indexCode string) (items []*MysteelChemic
 	return
 }
 
-// MoveBaseFromMysteelChemicalIndex 移动钢联化工指标分类
+// MoveBaseFromMysteelChemicalIndex 移动上海钢联指标分类
 func MoveBaseFromMysteelChemicalIndex(chartInfoId, classifyId int) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` UPDATE base_from_mysteel_chemical_index
@@ -267,7 +267,7 @@ func GetFirstBaseFromMysteelChemicalIndexByClassifyId(classifyId int) (item *Bas
 	return
 }
 
-// GetMysteelChemicalIndexCount 根据条件获取钢联化工数据
+// GetMysteelChemicalIndexCount 根据条件获取上海钢联数据
 func GetMysteelChemicalIndexCount(condition string, pars []interface{}) (count int, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT COUNT(1) AS count FROM base_from_mysteel_chemical_index WHERE 1=1 `
@@ -280,7 +280,7 @@ func GetMysteelChemicalIndexCount(condition string, pars []interface{}) (count i
 	return
 }
 
-// GetMysteelChemicalIndexList 根据分类id获取钢联化工频度数据列表
+// GetMysteelChemicalIndexList 根据分类id获取上海钢联频度数据列表
 func GetMysteelChemicalIndexList(condition string, pars []interface{}, startSize, pageSize int, orderDesc string) (items []*BaseFromMysteelChemicalIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM base_from_mysteel_chemical_index WHERE 1=1 `
@@ -393,7 +393,7 @@ type TerminalNum struct {
 	Num          int    `description:"num"`
 }
 
-// GetMysteelChemicalGroupTerminalNum 获取钢联化工指标的终端分布
+// GetMysteelChemicalGroupTerminalNum 获取上海钢联指标的终端分布
 func GetMysteelChemicalGroupTerminalNum() (items []*TerminalNum, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT terminal_code,count(1) num FROM base_from_mysteel_chemical_index GROUP BY terminal_code ORDER BY num ASC `
@@ -405,7 +405,7 @@ func GetMysteelChemicalGroupTerminalNum() (items []*TerminalNum, err error) {
 // @Description: 刷新配置的基础指标信息结构体
 type BaseRefreshEdbInfo struct {
 	EdbInfoId       int
-	ClassifyId      int    `description:"钢联化工指标分类id"`
+	ClassifyId      int    `description:"上海钢联指标分类id"`
 	IndexCode       string `description:"指标编码"`
 	IndexName       string `description:"指标名称"`
 	EndDate         string `description:"最新日期"`
@@ -426,7 +426,7 @@ type RefreshBaseEdbInfoResp struct {
 }
 
 // GetMysteelChemicalBaseInfoList
-// @Description: 获取钢联化工数据列表
+// @Description: 获取上海钢联数据列表
 // @author: Roc
 // @datetime 2024-01-10 14:28:35
 // @param condition string
@@ -468,7 +468,7 @@ func GetMysteelChemicalBaseInfoList(condition string, pars []interface{}, orderB
 }
 
 // ModifyMysteelChemicalUpdateStatus
-// @Description:  修改钢联化工数据停更状态
+// @Description:  修改上海钢联数据停更状态
 // @author: Roc
 // @datetime 2024-01-08 16:23:31
 // @param edbIdList []int

+ 7 - 6
models/data_manage/request/mysteel_chemical_data.go

@@ -1,13 +1,13 @@
 package request
 
-// AddBaseFromMysteelChemicalClassifyReq 添加钢联化工分类请求
+// AddBaseFromMysteelChemicalClassifyReq 添加上海钢联分类请求
 type AddBaseFromMysteelChemicalClassifyReq struct {
 	ParentId     int    `description:"上级id"`
 	ClassifyName string `description:"分类名称"`
 	Level        int    `description:"层级,第一级传0,其余传上一级的层级"`
 }
 
-// EditBaseFromMysteelChemicalClassifyReq 修改钢联化工分类请求
+// EditBaseFromMysteelChemicalClassifyReq 修改上海钢联分类请求
 type EditBaseFromMysteelChemicalClassifyReq struct {
 	ClassifyName                      string `description:"分类名称"`
 	BaseFromMysteelChemicalClassifyId int    `description:"分类id"`
@@ -21,12 +21,12 @@ type MoveBaseFromMysteelChemicalClassifyReq struct {
 	NextBaseFromMysteelChemicalClassifyId   int `description:"下一个兄弟节点分类id"`
 }
 
-// DelBaseFromMysteelChemicalClassifyReq 删除钢联化工分类请求
+// DelBaseFromMysteelChemicalClassifyReq 删除上海钢联分类请求
 type DelBaseFromMysteelChemicalClassifyReq struct {
 	BaseFromMysteelChemicalClassifyId int `description:"分类id"`
 }
 
-// AddBaseFromMysteelChemicalReq 添加钢联化工请求
+// AddBaseFromMysteelChemicalReq 添加上海钢联请求
 type AddBaseFromMysteelChemicalReqItem struct {
 	BaseFromMysteelChemicalIndexId    int    `description:"指标id"`
 	BaseFromMysteelChemicalClassifyId int    `description:"分类id"`
@@ -34,12 +34,13 @@ type AddBaseFromMysteelChemicalReqItem struct {
 	UpdateWeek                        string `description:"更新周期"`
 	UpdateTime                        string `description:"更新时间点,多个时间点用英文,隔开"`
 }
-// AddBaseFromMysteelChemicalReq 添加钢联化工请求
+
+// AddBaseFromMysteelChemicalReq 添加上海钢联请求
 type AddBaseFromMysteelChemicalReq struct {
 	List []AddBaseFromMysteelChemicalReqItem
 }
 
-// DelBaseFromMysteelChemicalReq 删除钢联化工请求
+// DelBaseFromMysteelChemicalReq 删除上海钢联请求
 type DelBaseFromMysteelChemicalReq struct {
 	BaseFromMysteelChemicalIndexId int `description:"指标id"`
 }

+ 1 - 1
models/db.go

@@ -324,7 +324,7 @@ func initEdbData() {
 		new(data_manage.EdbInfoCalculateMapping),
 		new(data_manage.PredictEdbConf),                  //预测指标配置
 		new(data_manage.BaseFromMysteelChemicalClassify), //预测指标配置
-		new(data_manage.BaseFromMysteelChemicalIndex),    //钢联化工
+		new(data_manage.BaseFromMysteelChemicalIndex),    //上海钢联
 		new(data_manage.BaseFromEiaSteoClassify),         // Eia steo 报告指标
 		new(data_manage.BaseFromEiaSteoIndex),            // Eia steo 报告指标分类
 		new(data_manage.PredictEdbRuleData),              //预测指标,动态规则的计算数据

+ 20 - 0
models/english_report.go

@@ -47,6 +47,8 @@ type EnglishReport struct {
 	ApproveId          int       `description:"审批ID"`
 	DetailImgUrl       string    `description:"报告详情长图地址"`
 	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string    `description:"报告详情PDF地址-手机端"`
 	EmailHasFail       int       `description:"是否存在邮件发送失败的记录: 0-否; 1-是"`
 }
 
@@ -276,6 +278,8 @@ type EnglishReportList struct {
 	ApproveTime        string    `description:"审批时间"`
 	DetailImgUrl       string    `description:"报告详情长图地址"`
 	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string    `description:"报告详情PDF地址-手机端"`
 }
 
 type EnglishReportListResp struct {
@@ -937,6 +941,20 @@ func ModifyEnglishReportImgUrl(reportId int, detailImgUrl string) (err error) {
 	return
 }
 
+func ModifyEnglishReportPdfUrlMobile(reportId int, detailPdfUrlMobile string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report SET detail_pdf_url_mobile=? WHERE id=? `
+	_, err = o.Raw(sql, detailPdfUrlMobile, reportId).Exec()
+	return
+}
+
+func ModifyEnglishReportImgUrlMobile(reportId int, detailImgUrlMobile string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE english_report SET detail_img_url_mobile=? WHERE id=? `
+	_, err = o.Raw(sql, detailImgUrlMobile, reportId).Exec()
+	return
+}
+
 // UpdatePdfUrlEnglishReportById 清空pdf相关字段
 func UpdatePdfUrlEnglishReportById(reportId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -989,5 +1007,7 @@ func FormatEnglishReport2ListItem(origin *EnglishReport) (item *EnglishReportLis
 	item.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, origin.ApproveTime)
 	item.DetailImgUrl = origin.DetailImgUrl
 	item.DetailPdfUrl = origin.DetailPdfUrl
+	item.DetailImgUrlMobile = origin.DetailImgUrlMobile
+	item.DetailPdfUrlMobile = origin.DetailPdfUrlMobile
 	return
 }

+ 1 - 0
models/ppt_english/ppt_english.go

@@ -25,6 +25,7 @@ type PptEnglish struct {
 	IsShare       int8      `description:"是否分享,0:不分享,1:分享"`
 	PublishTime   time.Time `description:"发布时间"`
 	CoverContent  string    `description:"PPT内容-JSON"`
+	TitleSetting  string    `description:"PPT标题设置"`
 }
 
 type PptEnglishItem struct {

+ 39 - 1
models/ppt_english/ppt_english_group_mapping.go

@@ -9,7 +9,7 @@ import (
 type PptEnglishGroupMapping struct {
 	GroupPptId      int64     `orm:"column(group_ppt_id);pk;auto" description:"自增序号"`
 	GroupId         int64     `description:"ppt目录ID"`
-	PptSort         int64     `description:"Ppt的排序"`
+	PptSort         float64   `description:"Ppt的排序"`
 	PptId           int64     `description:"ppt ID"`
 	CreateTime      time.Time `orm:"auto_now_add;type(datetime)" description:"创建时间"`
 	ModifyTime      time.Time `orm:"auto_now;type(datetime)" description:"修改时间"`
@@ -41,6 +41,14 @@ func GetPptMappingListByGroupId(groupId int64) (list []*PptEnglishGroupMapping,
 	return
 }
 
+// GetPptMappingListByGroupIdDesc 查询目录下,ppt列表, 降序排列
+func GetPptMappingListByGroupIdDesc(groupId int64) (list []*PptEnglishGroupMapping, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select group_ppt_id, group_id, ppt_id, ppt_sort, admin_id, admin_real_name, create_time from ppt_english_group_mapping where group_id=? order by ppt_sort desc, group_ppt_id desc `
+	_, err = o.Raw(sql, groupId).QueryRows(&list)
+	return
+}
+
 // GetPptMappingListByGroupIds 根据分组ID查找
 func GetPptMappingListByGroupIds(groupIds []int64) (list []*PptEnglishGroupMapping, err error) {
 	_, err = orm.NewOrmUsingDB("rddp").
@@ -151,3 +159,33 @@ func GetPublicGroupPptByPptIds(pptIds []string) (list []*PptEnglishGroupMapping,
 	_, err = o.Raw(sql).QueryRows(&list)
 	return
 }
+
+// GetMaxSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMaxSortByEnglishGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select MAX(ppt_sort) AS count from ppt_english_group_mapping where group_id=?`
+	err = o.Raw(sql, groupPptId).QueryRow(&pptSort)
+
+	return
+}
+
+// GetMinSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMinSortByEnglishGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select MIN(ppt_sort) AS count from ppt_english_group_mapping where group_id=?`
+	err = o.Raw(sql, groupPptId).QueryRow(&pptSort)
+
+	return
+}

+ 33 - 2
models/ppt_v2_group_mapping.go

@@ -9,7 +9,7 @@ import (
 type PptV2GroupMapping struct {
 	GroupPptId      int64     `orm:"column(group_ppt_id);pk;auto" description:"自增序号"`
 	GroupId         int64     `description:"ppt目录ID"`
-	PptSort         int64     `description:"Ppt的排序"`
+	PptSort         float64   `description:"Ppt的排序"`
 	PptId           int64     `description:"ppt ID"`
 	CreateTime      time.Time `orm:"auto_now_add;type(datetime)" description:"创建时间"`
 	ModifyTime      time.Time `orm:"auto_now;type(datetime)" description:"修改时间"`
@@ -36,7 +36,8 @@ func GetPptMappingCountByGroupId(groupId int64) (total int64, err error) {
 // GetPptMappingListByGroupId 查询目录下,ppt列表
 func GetPptMappingListByGroupId(groupId int64) (list []*PptV2GroupMapping, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `select group_ppt_id, group_id, ppt_id, ppt_sort, admin_id, admin_real_name, create_time from ppt_v2_group_mapping where group_id=? order by ppt_sort asc, group_ppt_id asc `
+	sql := `select a.group_ppt_id, a.group_id, a.ppt_id, a.ppt_sort, a.admin_id, a.admin_real_name, a.create_time from ppt_v2_group_mapping AS a
+            JOIN ppt_v2 b on a.ppt_id = b.ppt_id where a.group_id=? order by a.ppt_sort desc, b.modify_time desc `
 	_, err = o.Raw(sql, groupId).QueryRows(&list)
 	return
 }
@@ -151,3 +152,33 @@ func GetPublicGroupPptByPptIds(pptIds []string) (list []*PptV2GroupMapping, err
 	_, err = o.Raw(sql).QueryRows(&list)
 	return
 }
+
+// GetMaxSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMaxSortByGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select MAX(ppt_sort) AS count from ppt_v2_group_mapping where group_id=?`
+	err = o.Raw(sql, groupPptId).QueryRow(&pptSort)
+
+	return
+}
+
+// GetMinSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMinSortByGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select MIN(ppt_sort) AS count from ppt_v2_group_mapping where group_id=?`
+	err = o.Raw(sql, groupPptId).QueryRow(&pptSort)
+
+	return
+}

+ 54 - 9
models/report.go

@@ -2,12 +2,14 @@ package models
 
 import (
 	"errors"
+	"eta/eta_mobile/models/report"
 	"eta/eta_mobile/utils"
 	"fmt"
-	"github.com/beego/beego/v2/client/orm"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // 报告状态
@@ -66,6 +68,8 @@ type Report struct {
 	ApproveId          int       `description:"审批ID"`
 	DetailImgUrl       string    `description:"报告详情长图地址"`
 	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string    `description:"报告详情PDF地址-手机端"`
 
 	ContentStruct       string    `description:"内容组件"`
 	LastModifyAdminId   int       `description:"最后更新人ID"`
@@ -87,6 +91,9 @@ type Report struct {
 	ReportCreateTime    time.Time `description:"报告时间创建时间"`
 	InheritReportId     int       `description:"待继承的报告ID"`
 	VoiceGenerateType   int       `description:"音频生成方式,0:系统生成,1:人工上传"`
+	RaiReportId         int       `description:"RAI报告ID"`
+	MiniShow            int       `description:"是否在C端展示:0-否;1-是"`
+	FreeReportRatio     string    `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 type ReportList struct {
@@ -131,6 +138,8 @@ type ReportList struct {
 	ApproveTime        string                    `description:"审批时间"`
 	DetailImgUrl       string                    `description:"报告详情长图地址"`
 	DetailPdfUrl       string                    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string                    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string                    `description:"报告详情PDF地址-手机端"`
 
 	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
 	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
@@ -150,6 +159,9 @@ type ReportList struct {
 	ClassifyNameThird   string    `description:"三级分类名称"`
 	InheritReportId     int       `description:"待继承的报告ID"`
 	IsCollect           int       `description:"是否收藏"`
+	RaiReportId         int       `description:"RAI报告ID"`
+	MiniShow            int       `description:"是否在C端展示:0-否;1-是"`
+	FreeReportRatio     string    `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 type ReportListResp struct {
@@ -372,6 +384,10 @@ type ReportDetail struct {
 	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
 	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
 	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	FreeLayoutConfig    string    `description:"自由布局配置"`
+	RaiReportId         int       `description:"RAI报告ID"`
+	MiniShow            int       `description:"是否在C端展示:0-否;1-是"`
+	FreeReportRatio     string    `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 func GetReportById(reportId int) (item *ReportDetail, err error) {
@@ -516,6 +532,8 @@ type AddReq struct {
 	IsPublicPublish    int8   `description:"是否公开发布,1:是,2:否"`
 	InheritReportId    int    `description:"待继承的报告ID"`
 	GrantAdminIdList   []int  `description:"授权用户id列表"`
+	MiniShow           int    `description:"是否在C端展示:0-否;1-是"`
+	FreeReportRatio    string `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 type PrePublishReq struct {
@@ -560,8 +578,10 @@ type EditReq struct {
 	EndResourceId      int    `description:"版尾资源ID"`
 	//CollaborateType    int8   `description:"协作方式,1:个人,2:多人协作。默认:1"`
 	//ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
-	IsPublicPublish  int8  `description:"是否公开发布,1:是,2:否"`
-	GrantAdminIdList []int `description:"授权用户id列表"`
+	IsPublicPublish  int8   `description:"是否公开发布,1:是,2:否"`
+	GrantAdminIdList []int  `description:"授权用户id列表"`
+	MiniShow         int    `description:"是否在C端展示:0-否;1-是"`
+	FreeReportRatio  string `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 type EditResp struct {
@@ -831,9 +851,10 @@ func (reportInfo *Report) UpdateReport(cols []string) (err error) {
 // @Description: 晨周报详情
 type ReportDetailView struct {
 	*ReportDetail
-	ChapterList    []*ReportChapter
-	GrandAdminList []ReportDetailViewAdmin
-	PermissionList []ReportDetailViewPermission
+	ChapterList            []*ReportChapter
+	GrandAdminList         []ReportDetailViewAdmin
+	PermissionList         []ReportDetailViewPermission
+	FreeLayoutContentPages []*report.ContentPage
 }
 
 // ReportDetailViewAdmin
@@ -878,6 +899,7 @@ type ElasticReportDetail struct {
 	BodyContent        string `description:"内容"`
 	PublishTime        string `description:"发布时间"`
 	PublishState       int    `description:"发布状态 1-未发布 2-已发布"`
+	IsPublicPublish    int8   `description:"是否公开发布,1:是,2:否"`
 	Author             string `description:"作者"`
 	ClassifyIdFirst    int    `description:"一级分类ID"`
 	ClassifyNameFirst  string `description:"一级分类名称"`
@@ -1432,6 +1454,20 @@ func ModifyReportImgUrl(reportId int, detailImgUrl string) (err error) {
 	return
 }
 
+func ModifyReportPdfUrlMobile(reportId int, detailPdfUrlMobile string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET detail_pdf_url_mobile=? WHERE id=? `
+	_, err = o.Raw(sql, detailPdfUrlMobile, reportId).Exec()
+	return
+}
+
+func ModifyReportImgUrlMobile(reportId int, detailImgUrlMobile string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET detail_img_url_mobile=? WHERE id=? `
+	_, err = o.Raw(sql, detailImgUrlMobile, reportId).Exec()
+	return
+}
+
 // UpdatePdfUrlReportById 清空pdf相关字段
 func UpdatePdfUrlReportById(reportId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -1785,11 +1821,11 @@ func GetReportListByCollectListV2(classifyIdFirst, classifyIdSecond, classifyIdT
 	sql := `
 		SELECT DISTINCT t.id, t.title, t.author, t.modify_time, t.publish_time,t.classify_id_first,t.classify_name_first,
 t.classify_id_second,t.classify_name_second,t.classify_id_third,t.classify_name_third,
-t.abstract,t.admin_id,t.admin_real_name,t.last_modify_admin_id,t.last_modify_admin_name 
+t.abstract,t.admin_id,t.admin_real_name,t.last_modify_admin_id,t.last_modify_admin_name,report_layout
 		FROM (
 			SELECT b.id, b.title, b.author, b.modify_time, b.publish_time,b.classify_id_first,b.classify_name_first,
 b.classify_id_second,b.classify_name_second,b.classify_id_third,b.classify_name_third,
-b.abstract,b.admin_id,b.admin_real_name,b.last_modify_admin_id,b.last_modify_admin_name 
+b.abstract,b.admin_id,b.admin_real_name,b.last_modify_admin_id,b.last_modify_admin_name ,report_layout
 			FROM report AS b WHERE 1 = 1
 	`
 
@@ -1883,3 +1919,12 @@ b.abstract,b.admin_id,b.admin_real_name,b.last_modify_admin_id,b.last_modify_adm
 	_, err = o.Raw(sql, params...).QueryRows(&items)
 	return items, err
 }
+
+type ReportShartUrlReq struct {
+	Url      string `description:"分享链接"`
+	ReportId int    `description:"报告ID"`
+}
+
+type ReportShartUrlResp struct {
+	UrlToken string `description:"分享链接token"`
+}

+ 134 - 0
models/report/report_free_layout.go

@@ -0,0 +1,134 @@
+package report
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type ReportFreeLayout struct {
+	Id              int       `gorm:"primaryKey;autoIncrement;column:id"` // 主键
+	ReportId        int       `gorm:"column:report_id"`                   // 研报Id
+	ReportChapterId int       `gorm:"column:report_chapter_id"`           // 章节Id
+	Page            int       `gorm:"column:page"`                        // 页码
+	IsChapter       int       `gorm:"column:is_chapter"`                  // 是否多章节
+	Content         string    `gorm:"column:content;size:255"`            // 内容
+	ContentStruct   string    `gorm:"column:content_struct;size:255"`     // 内容
+	CreateTime      time.Time `gorm:"column:create_time"`                 // 创建时间
+	ModifyTime      time.Time `gorm:"column:modify_time"`                 // 修改时间
+}
+type PagePositionEnum string
+
+const (
+	Left   PagePositionEnum = "left"
+	Right  PagePositionEnum = "right"
+	Center PagePositionEnum = "center"
+)
+
+type ContentPage struct {
+	Id              int    `json:"Id"`
+	Page            int    `json:"Page"`
+	Content         string `json:"Content"`
+	ContentStruct   string `json:"ContentStruct"`
+	ReportId        int    `json:"ChapterId"`
+	ReportChapterId int    `json:"ReportChapterId"`
+}
+
+func (cp *ContentPage) ToView(isChapter bool, ReportId int, ReportChapterId int) *ReportFreeLayout {
+	if isChapter {
+		return &ReportFreeLayout{
+			ReportId:        ReportId,
+			ReportChapterId: ReportChapterId,
+			Page:            cp.Page,
+			IsChapter:       1,
+			Content:         cp.Content,
+			ContentStruct:   cp.ContentStruct,
+			CreateTime:      time.Now(),
+		}
+	} else {
+		return &ReportFreeLayout{
+			ReportId:        ReportId,
+			ReportChapterId: ReportChapterId,
+			Page:            cp.Page,
+			IsChapter:       0,
+			Content:         cp.Content,
+			ContentStruct:   cp.ContentStruct,
+			CreateTime:      time.Now(),
+		}
+	}
+}
+func (cp *ReportFreeLayout) ToPageView() *ContentPage {
+	return &ContentPage{
+		Page:            cp.Page,
+		Content:         cp.Content,
+		ContentStruct:   cp.ContentStruct,
+		ReportId:        cp.ReportId,
+		ReportChapterId: cp.ReportChapterId,
+	}
+}
+
+func ToPageViewList(srcList []*ReportFreeLayout) (list []*ContentPage) {
+	for _, v := range srcList {
+		list = append(list, v.ToPageView())
+	}
+	return
+}
+
+// TableName 设置表名
+func (*ReportFreeLayout) TableName() string {
+	return "report_free_layout"
+}
+
+func GetFreeLayoutChapterPagesByReportId(reportId int) (list []*ContentPage, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var ormList []*ReportFreeLayout
+	sql := `select rfl.*,rc.sort from report_free_layout rfl LEFT JOIN report_chapter rc on rc.report_id=rfl.report_id and rc.report_chapter_id=rfl.report_chapter_id where rfl.report_id =? order by rc.sort,rfl.page asc`
+	_, err = o.Raw(sql, reportId).QueryRows(&ormList)
+	if err != nil {
+		return nil, err
+	}
+	list = ToPageViewList(ormList)
+	return
+}
+
+func GetFreeLayoutPagesByReportId(id int) (list []*ContentPage, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var ormList []*ReportFreeLayout
+	sql := `select * from report_free_layout  where report_id =? and is_chapter=0 order by page asc`
+	_, err = o.Raw(sql, id).QueryRows(&ormList)
+	if err != nil {
+		return nil, err
+	}
+	list = ToPageViewList(ormList)
+	return
+}
+
+// GetReportFreeLayoutListByReportId
+// @Description: 根据报告ID和章节ID获取所有的布局列表
+// @author: Roc
+// @datetime 2025-04-16 13:46:38
+// @param reportId int
+// @param chapterId int
+// @return list []*ReportFreeLayout
+// @return err error
+func GetReportFreeLayoutListByReportId(reportId, chapterId int) (list []*ReportFreeLayout, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from report_free_layout  where report_id =? and report_chapter_id=? order by page asc`
+	_, err = o.Raw(sql, reportId, chapterId).QueryRows(&list)
+
+	return
+}
+
+// GetAllReportFreeLayoutListByReportId
+// @Description: 根据报告id获取所有的报告自由布局列表(含章节的)
+// @author: Roc
+// @datetime 2025-04-16 13:46:24
+// @param reportId int
+// @return list []*ReportFreeLayout
+// @return err error
+func GetAllReportFreeLayoutListByReportId(reportId int) (list []*ReportFreeLayout, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from report_free_layout  where report_id =? order by page asc`
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}

+ 6 - 3
models/report_chapter.go

@@ -94,6 +94,7 @@ type ReportChapterItemResp struct {
 	EndImg           string `description:"报告尾图地址"`
 	HeadStyle        string `description:"版头样式"`
 	EndStyle         string `description:"版尾样式"`
+	FreeReportRatio  string `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 type ReportChapterResp struct {
@@ -446,9 +447,11 @@ func CountReportChapterByTypeId(typeId int) (count int, err error) {
 // AddReportChapter
 // @Description: 待添加的报告章节
 type AddReportChapter struct {
-	ReportChapter       *ReportChapter
-	GrantList           []*report.ReportChapterGrant
-	GrantPermissionList []*report.ReportChapterPermissionMapping
+	ReportChapter               *ReportChapter
+	GrantList                   []*report.ReportChapterGrant
+	GrantPermissionList         []*report.ReportChapterPermissionMapping
+	ReportChapterFreeLayoutList []*report.ReportFreeLayout
+	InheritReportChapterId      int `description:"继承的章节id"`
 }
 
 // EditReportChapterBaseInfoAndPermissionReq

+ 26 - 2
models/report_v2.go

@@ -16,7 +16,7 @@ import (
 // @param addReportChapterList []AddReportChapter
 // @return reportId int64
 // @return err error
-func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGrant, addReportChapterList []AddReportChapter) (reportId int64, err error) {
+func AddReportAndChapter(reportItem *Report, reportFreeLayoutList []*report.ReportFreeLayout, allGrantUserList []*report.ReportGrant, addReportChapterList []AddReportChapter) (reportId int64, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
@@ -37,6 +37,17 @@ func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGr
 	}
 	reportItem.Id = int(reportId)
 
+	// 新增报告分页内容
+	if len(reportFreeLayoutList) > 0 {
+		for _, reportFreeLayout := range reportFreeLayoutList {
+			reportFreeLayout.ReportId = int(reportId)
+		}
+		_, err = to.InsertMulti(utils.MultiAddNum, reportFreeLayoutList)
+		if err != nil {
+			return
+		}
+	}
+
 	// 新增报告授权
 	if len(allGrantUserList) > 0 {
 		for _, v := range allGrantUserList {
@@ -85,6 +96,19 @@ func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGr
 				}
 			}
 
+			// 新增报告章节分页内容
+			if len(addReportChapter.ReportChapterFreeLayoutList) > 0 {
+				reportChapterFreeLayoutList := addReportChapter.ReportChapterFreeLayoutList
+				for _, reportChapterFreeLayout := range reportChapterFreeLayoutList {
+					reportChapterFreeLayout.ReportId = int(reportId)
+					reportChapterFreeLayout.ReportChapterId = chapterItem.ReportChapterId
+				}
+				_, err = to.InsertMulti(utils.MultiAddNum, reportChapterFreeLayoutList)
+				if err != nil {
+					return
+				}
+			}
+
 		}
 	}
 
@@ -369,7 +393,7 @@ func GetReportListCountByAuthorized(condition string, pars []interface{}) (count
 func GetReportListByAuthorized(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 
-	sql := `SELECT id,classify_id_first,classify_name_first,classify_id_second,classify_name_second,classify_id_third,classify_name_third,title,stage,create_time,author,report_layout,collaborate_type,is_public_publish,abstract,has_chapter,publish_time FROM report as a WHERE 1=1  `
+	sql := `SELECT id,classify_id_first,classify_name_first,classify_id_second,classify_name_second,classify_id_third,classify_name_third,title,stage,create_time,author,report_layout,collaborate_type,is_public_publish,abstract,has_chapter,publish_time,mini_show,free_report_ratio FROM report as a WHERE 1=1  `
 	if condition != "" {
 		sql += condition
 	}

+ 3 - 2
models/smart_report/smart_report.go

@@ -51,6 +51,8 @@ type SmartReport struct {
 	MsgSendTime         time.Time `description:"模版消息发送时间"`
 	DetailImgUrl        string    `description:"报告详情长图地址"`
 	DetailPdfUrl        string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile  string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile  string    `description:"报告详情PDF地址-手机端"`
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
 	HeadImg             string    `description:"报告头图地址"`
@@ -419,11 +421,10 @@ func UpdateSmartReportsStateBySecondIds(oldState, newState int, secondIds []int)
 	return
 }
 
-
 // UpdatePdfUrlSmartReportById 清空pdf相关字段
 func UpdatePdfUrlSmartReportById(reportId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `UPDATE smart_report SET detail_img_url = '',detail_pdf_url='',modify_time=NOW() WHERE smart_report_id = ? `
 	_, err = o.Raw(sql, reportId).Exec()
 	return
-}
+}

+ 1 - 1
models/system/sys_user.go

@@ -57,7 +57,7 @@ type Admin struct {
 	OpenId                    string    `description:"弘则部门公众号的openid"`
 	UnionId                   string    `description:"微信公众平台唯一标识"`
 	EdbPermission             int8      `description:"指标库操作权限,0:只能操作 自己的,1:所有指标可操作"`
-	MysteelChemicalPermission int8      `description:"钢联化工指标操作权限,0:只能操作 自己的,1:所有指标可操作"`
+	MysteelChemicalPermission int8      `description:"上海钢联指标操作权限,0:只能操作 自己的,1:所有指标可操作"`
 	PredictEdbPermission      int8      `description:"预测指标库操作权限,0:只能操作 自己的,1:所有预测指标可操作"`
 	Province                  string    `description:"省"`
 	ProvinceCode              string    `description:"省编码"`

+ 45 - 0
routers/commentsRouter.go

@@ -7,6 +7,24 @@ import (
 
 func init() {
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelFrameworkController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelFrameworkController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/framework/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/chart/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/correlation:CorrelationChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/correlation:CorrelationChartClassifyController"],
         beego.ControllerComments{
             Method: "AddChartClassify",
@@ -2068,6 +2086,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "GeneralChartToken",
+            Router: `/chart_info/common/general_token`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "ChartInfoConvertDetail",
@@ -5524,6 +5551,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportCommonController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportCommonController"],
+        beego.ControllerComments{
+            Method: "ShareTransform",
+            Router: `/share/link`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
         beego.ControllerComments{
             Method: "CheckDayWeekReportChapterVideo",
@@ -5947,6 +5983,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "ShareGenerate",
+            Router: `/share/generate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
         beego.ControllerComments{
             Method: "ThsSendTemplateMsg",

+ 11 - 0
routers/router.go

@@ -10,6 +10,7 @@ package routers
 import (
 	"eta/eta_mobile/controllers"
 	"eta/eta_mobile/controllers/data_manage"
+	"eta/eta_mobile/controllers/data_manage/ai_predict_model"
 	"eta/eta_mobile/controllers/data_manage/correlation"
 	"eta/eta_mobile/controllers/data_manage/cross_variety"
 	"eta/eta_mobile/controllers/data_manage/data_manage_permission"
@@ -33,6 +34,8 @@ import (
 	"eta/eta_mobile/controllers/smart_report"
 	"eta/eta_mobile/controllers/speech_recognition"
 	"eta/eta_mobile/controllers/trade_analysis"
+	"eta/eta_mobile/services"
+
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/filter/cors"
 )
@@ -46,6 +49,8 @@ func init() {
 		ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
 		AllowCredentials: true,
 	}))
+
+	web.InsertFilter("/v1/share/*", web.BeforeRouter, services.FilterShareUrl())
 	ns := web.NewNamespace("/v1",
 		web.NSNamespace("/ppt",
 			web.NSInclude(
@@ -333,6 +338,12 @@ func init() {
 				&eta_forum.EtaForumController{},
 			),
 		),
+		web.NSNamespace("/ai_predict_model",
+			web.NSInclude(
+				&ai_predict_model.AiPredictModelIndexController{},
+				&ai_predict_model.AiPredictModelFrameworkController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 221 - 0
services/ai_predict_model_index.go

@@ -0,0 +1,221 @@
+package services
+
+import (
+	"encoding/json"
+	aiPredictModel "eta/eta_mobile/models/ai_predict_model"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"sort"
+	"time"
+)
+
+func GetAiPredictChartDetailByData(indexItem *aiPredictModel.AiPredictModelIndex, indexData []*aiPredictModel.AiPredictModelData, source int) (resp *data_manage.ChartInfoDetailResp, err error) {
+	resp = new(data_manage.ChartInfoDetailResp)
+
+	// 标的配置
+	var extraConfig aiPredictModel.AiPredictModelIndexExtraConfig
+	if indexItem.ExtraConfig != "" {
+		if e := json.Unmarshal([]byte(indexItem.ExtraConfig), &extraConfig); e != nil {
+			err = fmt.Errorf("标的额外配置解析失败, Config: %s, Err: %v", indexItem.ExtraConfig, e)
+			return
+		}
+	}
+
+	// 图表信息
+	var predictLegendName, confLeftMin, confLeftMax, unit string
+	if source == aiPredictModel.ModelDataSourceDaily {
+		predictLegendName = extraConfig.DailyChart.PredictLegendName
+		if predictLegendName == "" {
+			predictLegendName = "Predicted"
+		}
+		unit = extraConfig.DailyChart.Unit
+		confLeftMin = extraConfig.DailyChart.LeftMin
+		confLeftMax = extraConfig.DailyChart.LeftMax
+	}
+	if source == aiPredictModel.ModelDataSourceMonthly {
+		predictLegendName = "预测值"
+		unit = extraConfig.MonthlyChart.Unit
+		confLeftMin = extraConfig.MonthlyChart.LeftMin
+		confLeftMax = extraConfig.MonthlyChart.LeftMax
+	}
+	// 这里简单兼容下吧,暂时就不修数据了
+	if confLeftMin == "" {
+		confLeftMin = indexItem.LeftMin
+	}
+	if confLeftMax == "" {
+		confLeftMax = indexItem.LeftMax
+	}
+
+	// 获取指标对应的图表
+	chartSourceMapping := map[int]int{
+		aiPredictModel.ModelDataSourceMonthly: utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY,
+		aiPredictModel.ModelDataSourceDaily:   utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY,
+	}
+	chartInfo, e := data_manage.GetAiPredictChartInfoByIndexId(chartSourceMapping[source], indexItem.AiPredictModelIndexId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取标的图表失败, %v", e)
+		return
+	}
+
+	// 获取曲线图主题样式
+	chartView := new(data_manage.ChartInfoView)
+	if chartInfo != nil && chartInfo.ChartInfoId > 0 {
+		chartView.ChartInfoId = chartInfo.ChartInfoId
+		chartView.ChartName = chartInfo.ChartName
+		chartView.ChartNameEn = chartInfo.ChartNameEn
+		chartView.Source = chartInfo.Source
+		chartView.ChartImage = chartInfo.ChartImage
+	} else {
+		chartView.ChartName = indexItem.IndexName
+		chartView.ChartNameEn = indexItem.IndexName
+	}
+	chartView.ChartType = utils.CHART_SOURCE_DEFAULT
+	chartTheme, e := data.GetChartThemeConfig(0, chartView.ChartType, utils.CHART_TYPE_CURVE)
+	if e != nil {
+		err = fmt.Errorf("获取图表主题样式失败, %v", e)
+		return
+	}
+	chartView.ChartThemeStyle = chartTheme.Config
+	chartView.ChartThemeId = chartTheme.ChartThemeId
+
+	chartView.ChartName = indexItem.IndexName
+	chartView.ChartNameEn = indexItem.IndexName
+	chartView.DateType = 3
+	chartView.Calendar = "公历"
+	chartView.ChartSource = "AI预测模型"
+	chartView.ChartSourceEn = "AI预测模型"
+	chartView.Unit = unit
+	chartView.UnitEn = unit
+
+	// EdbList-固定一条为标的实际值、一条为预测值
+	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	edbActual, edbPredict := new(data_manage.ChartEdbInfoMapping), new(data_manage.ChartEdbInfoMapping)
+	edbActual.EdbName = indexItem.IndexName
+	edbActual.EdbNameEn = indexItem.IndexName
+	edbActual.IsAxis = 1
+	edbActual.Unit = unit
+	edbActual.UnitEn = unit
+
+	edbPredict.EdbName = predictLegendName
+	edbPredict.EdbNameEn = predictLegendName
+	edbPredict.IsAxis = 1
+	edbPredict.Unit = unit
+	edbPredict.UnitEn = unit
+	actualData, predictData := make([]*data_manage.EdbDataList, 0), make([]*data_manage.EdbDataList, 0)
+
+	var startDate, endDate time.Time
+	var actualValues, predictValues []float64
+	var actualNewest, predictNewest bool
+	var actualLatestTimestamp int64 // 实际值最后一天的时间戳,作为日度图表的分割线
+	for k, v := range indexData {
+		// 如果实际值和预测值都是null那么该日期无效直接忽略
+		if !v.Value.Valid && !v.PredictValue.Valid {
+			continue
+		}
+
+		// 将有效值加入[]float64,最后取极值
+		if v.Value.Valid {
+			actualValues = append(actualValues, v.Value.Float64)
+		}
+		if v.PredictValue.Valid {
+			predictValues = append(predictValues, v.PredictValue.Float64)
+		}
+
+		// 开始结束时间
+		if k == 0 {
+			startDate = v.DataTime
+			endDate = v.CreateTime
+		}
+		if v.DataTime.Before(startDate) {
+			startDate = v.DataTime
+		}
+		if v.DataTime.After(endDate) {
+			endDate = v.DataTime
+		}
+
+		// 指标数据
+		if v.Value.Valid {
+			if !actualNewest {
+				edbActual.LatestDate = v.DataTime.Format(utils.FormatDate)
+				edbActual.LatestValue = v.Value.Float64
+				actualLatestTimestamp = v.DataTime.UnixNano() / 1e6
+				actualNewest = true
+			}
+			actualData = append(actualData, &data_manage.EdbDataList{
+				DataTime:      v.DataTime.Format(utils.FormatDate),
+				Value:         v.Value.Float64,
+				DataTimestamp: v.DataTimestamp,
+			})
+		}
+		if v.PredictValue.Valid {
+			if !predictNewest {
+				edbPredict.LatestDate = v.DataTime.Format(utils.FormatDate)
+				edbPredict.LatestValue = v.Value.Float64
+				predictNewest = true
+			}
+			predictData = append(predictData, &data_manage.EdbDataList{
+				DataTime:      v.DataTime.Format(utils.FormatDate),
+				Value:         v.PredictValue.Float64,
+				DataTimestamp: v.DataTimestamp,
+			})
+		}
+	}
+
+	// 图表数据这里均做一个升序排序
+	sort.Slice(actualData, func(i, j int) bool {
+		return actualData[i].DataTimestamp < actualData[j].DataTimestamp
+	})
+	sort.Slice(predictData, func(i, j int) bool {
+		return predictData[i].DataTimestamp < predictData[j].DataTimestamp
+	})
+
+	// 极值
+	actualMin, actualMax := utils.FindMinMax(actualValues)
+	predictMin, predictMax := utils.FindMinMax(predictValues)
+	edbActual.MinData = actualMin
+	edbActual.MaxData = actualMax
+	edbPredict.MinData = predictMin
+	edbPredict.MaxData = predictMax
+
+	edbActual.DataList = actualData
+	edbPredict.DataList = predictData
+	edbList = append(edbList, edbActual, edbPredict)
+
+	// 上下限
+	if confLeftMin != "" {
+		chartView.LeftMin = confLeftMin
+	} else {
+		leftMin := actualMin
+		if leftMin > predictMin {
+			leftMin = predictMin
+		}
+		chartView.LeftMin = fmt.Sprint(leftMin)
+	}
+	if confLeftMax != "" {
+		chartView.LeftMax = confLeftMax
+	} else {
+		leftMax := actualMax
+		if leftMax < predictMax {
+			leftMax = predictMax
+		}
+		chartView.LeftMax = fmt.Sprint(leftMax)
+	}
+
+	chartView.StartDate = startDate.Format(utils.FormatDate)
+	chartView.EndDate = endDate.Format(utils.FormatDate)
+
+	// 日度图表的分割线日期
+	if source == aiPredictModel.ModelDataSourceDaily {
+		var dataResp struct {
+			ActualLatestTimestamp int64
+		}
+		dataResp.ActualLatestTimestamp = actualLatestTimestamp
+		resp.DataResp = dataResp
+	}
+
+	resp.ChartInfo = chartView
+	resp.EdbInfoList = edbList
+	return
+}

+ 9 - 11
services/data/base_edb_lib.go

@@ -88,32 +88,30 @@ func AddEdbData(source int, edbCode, frequency string) (resp *models.BaseRespons
 	return
 }
 
-// AddEdbData 新增指标数据
-func AddEdbDataWindWsd(source int, stockCode, edbCode string) (resp *models.BaseResponse, err error) {
+// AddEdbDataWindWsd 新增指标数据
+func AddEdbDataWindWsd(source int, stockCode, edbCode, days, period, priceAdj string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["StockCode"] = stockCode
 	param["Source"] = source
+	param["Days"] = days
+	param["Period"] = period
+	param["PriceAdj"] = priceAdj
 	urlStr := `wind/wsd/add`
-	if urlStr == "" {
-		err = fmt.Errorf("未实现该指标的刷新接口,请联系管理员")
-		return
-	}
 	resp, err = postRefreshEdbData(param, urlStr)
 	return
 }
 
 // AddEdbDataThsDs 新增指标数据
-func AddEdbDataThsDs(source int, stockCode, edbCode string) (resp *models.BaseResponse, err error) {
+func AddEdbDataThsDs(source int, stockCode, edbCode, extraPars, days, period string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["StockCode"] = stockCode
 	param["Source"] = source
+	param["Days"] = days
+	param["Period"] = period
+	param["ExtraPars"] = extraPars
 	urlStr := `ths/ds/add`
-	if urlStr == "" {
-		err = fmt.Errorf("未实现该指标的刷新接口,请联系管理员")
-		return
-	}
 	resp, err = postRefreshEdbData(param, urlStr)
 	return
 }

+ 120 - 7
services/data/chart_classify.go

@@ -5,9 +5,11 @@ import (
 	"eta/eta_mobile/models/data_manage"
 	"eta/eta_mobile/models/system"
 	"eta/eta_mobile/services/data/data_manage_permission"
+	"eta/eta_mobile/services/eta_forum"
 	"eta/eta_mobile/utils"
 	"fmt"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -239,7 +241,7 @@ func HandleNoPermissionChart(allNodes []*data_manage.ChartClassifyItems, noPermi
 func AddChartClassify(chartClassifyName string, parentId, level, source int, lang string, sysUser *system.Admin) (classifyInfo *data_manage.ChartClassify, err error, errMsg string, isSendEmail bool) {
 	isSendEmail = true
 	errMsg = "保存分类失败"
-
+	isSelected := 0
 	// 校验分类名称相同的数量
 	{
 		var count int
@@ -263,7 +265,25 @@ func AddChartClassify(chartClassifyName string, parentId, level, source int, lan
 
 	//获取该层级下最大的排序数
 	maxSort, err := data_manage.GetChartClassifyMaxSort(parentId, source)
-
+//查询顶级rootId
+	rootId := 0
+	levelPath := ""
+	if parentId > 0 {
+		parentClassify, tErr := data_manage.GetChartClassifyById(parentId)
+		if tErr != nil {
+			if tErr.Error() == utils.ErrNoRow() {
+				errMsg = "父级分类不存在"
+				err = errors.New(errMsg)
+				return
+			}
+			errMsg = "获取失败"
+			err = errors.New("获取分类信息失败,Err:" + tErr.Error())
+			return
+		}
+		rootId = parentClassify.RootId
+		levelPath = parentClassify.LevelPath
+		isSelected = parentClassify.IsSelected
+	}
 	classifyInfo = new(data_manage.ChartClassify)
 	classifyInfo.ParentId = parentId
 	classifyInfo.ChartClassifyName = chartClassifyName
@@ -278,15 +298,36 @@ func AddChartClassify(chartClassifyName string, parentId, level, source int, lan
 	classifyInfo.UniqueCode = utils.MD5(utils.DATA_PREFIX + "_" + timestamp)
 	classifyInfo.Sort = maxSort + 1
 	classifyInfo.Source = source
-
-	_, err = data_manage.AddChartClassify(classifyInfo)
+	classifyInfo.RootId = rootId
+	classifyInfo.IsSelected = isSelected
+	newId, err := data_manage.AddChartClassify(classifyInfo)
 	if err != nil {
 		return
 	}
-
+	updateCols := make([]string, 0)
+	if parentId == 0 { //一级目录的rootId等于自己本身
+		classifyInfo.RootId = int(newId)
+		updateCols = append(updateCols, "RootId")
+	}
+	if parentId > 0 {
+		levelPath = fmt.Sprintf("%s%d,", levelPath, newId)
+	} else {
+		levelPath = fmt.Sprintf("%d,", newId)
+	}
+	updateCols = append(updateCols, "LevelPath")
+	classifyInfo.LevelPath = levelPath
+	classifyInfo.ChartClassifyId = int(newId)
+	err = classifyInfo.Update(updateCols)
+	if err != nil {
+		errMsg = "更新分类失败"
+		return
+	}
 	// 目前只有ETA图库需要继承分类权限
 	if classifyInfo.Source == utils.CHART_SOURCE_DEFAULT {
 		go data_manage_permission.InheritParentClassify(5, classifyInfo.Source, classifyInfo.ChartClassifyId, classifyInfo.ParentId, classifyInfo.ChartClassifyName)
+		if isSelected == utils.ChartClassifyIsSelected { // 如果分类设置为精选资源,则需要同步到ETA资源库
+			go eta_forum.ChartClassifySave(classifyInfo.ChartClassifyId)
+		}
 	}
 
 	return
@@ -308,13 +349,12 @@ func AddChartClassify(chartClassifyName string, parentId, level, source int, lan
 func EditChartClassify(chartClassifyId, source int, chartClassifyName, lang string, sysUser *system.Admin) (classifyInfo *data_manage.ChartClassify, err error, errMsg string, isSendEmail bool) {
 	isSendEmail = true
 	errMsg = "保存失败"
-
+	
 	// 获取分类信息
 	classifyInfo, err = data_manage.GetChartClassifyById(chartClassifyId)
 	if err != nil {
 		return
 	}
-
 	// 分类来源校验
 	if classifyInfo.Source != source {
 		errMsg = "图表分类异常"
@@ -422,3 +462,76 @@ func GetChartClassifyParentRecursive(list []*data_manage.ChartClassifyItems, cla
 	}
 	return res
 }
+
+// 新增处理子分类精选状态的函数
+func UpdateChildClassifySelection(classifyInfo *data_manage.ChartClassify, parentClassifyInfo *data_manage.ChartClassify, oldSelected int) error {
+	// 处理一级目录
+	if classifyInfo.ParentId == 0 {
+		if oldSelected != classifyInfo.IsSelected {
+			return data_manage.UpdateChartClassifyIsSelected(
+				classifyInfo.Source,
+				classifyInfo.IsSelected,
+				classifyInfo.LevelPath,
+			)
+		}
+		return nil
+	}else {
+		// 处理二级及以上目录
+		if classifyInfo.IsSelected != parentClassifyInfo.IsSelected {
+			return data_manage.UpdateChartClassifyIsSelected(
+				classifyInfo.Source,
+				parentClassifyInfo.IsSelected,
+				classifyInfo.LevelPath,
+			)
+		}
+	}
+
+	
+	return nil
+}
+
+// 新增内部函数
+func UpdateChartClassifyLevelPathWithChildren(chartClassifyInfo *data_manage.ChartClassify, parentChartClassifyInfo *data_manage.ChartClassify, oldParentId int, oldLevelPath string) error {
+	levelPath := fmt.Sprintf("%s%d,", parentChartClassifyInfo.LevelPath, chartClassifyInfo.ChartClassifyId)
+	chartClassifyInfo.LevelPath = levelPath
+	if err := chartClassifyInfo.Update([]string{"LevelPath"}); err != nil {
+		return fmt.Errorf("修改失败,Err:" + err.Error())
+	}
+
+	// 更新子分类的levelpath
+	tmpList, err := data_manage.GetChartClassifyByLevelPath(oldLevelPath, chartClassifyInfo.Source)
+	if err != nil {
+		return fmt.Errorf("保存分类失败,Err:" + err.Error())
+	}
+    // 把原先的父级levePath,替换成最新的父级序列
+	for _, tmp := range tmpList {
+		after, _ := strings.CutPrefix(tmp.LevelPath, oldLevelPath)
+		if after != "" {
+			tmp.LevelPath = levelPath + after
+			tmp.ModifyTime = time.Now()
+			if e := tmp.Update([]string{"LevelPath", "ModifyTime"}); e != nil {
+				return fmt.Errorf("修改子分类,Err:" + e.Error())
+			}
+		}
+	}
+	return nil
+}
+
+// GetChartClassifyChildrenRecursive 根据父目录递归子级目录
+func GetChartClassifyChildrenRecursive(list []*data_manage.ChartClassifyItems, parentId int) []*data_manage.ChartClassifyItems {
+	var res []*data_manage.ChartClassifyItems
+
+	for _, v := range list {
+		if v.ParentId == parentId {
+			// 递归调用以获取更深层次的子级
+			children := GetChartClassifyChildrenRecursive(list, v.ChartClassifyId)
+			// 将当前节点和所有子节点添加到结果中
+			res = append(res, v)
+			res = append(res, children...)
+		} else if v.ChartClassifyId == parentId {
+			// 将当前节点添加到结果中
+			res = append(res, v)
+		}
+	}
+	return res
+}

+ 47 - 67
services/data/chart_info.go

@@ -619,7 +619,16 @@ func GetEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
-
+	// 设置季节性图的左右轴
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	// 如果是季节性图则进入特殊排序
+	if chartType == 2 {
+		// 根据设置的左右轴,对mappingList进行排序,1左轴排在前面,0右轴排在前面
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
 		item := new(data_manage.ChartEdbInfoMapping)
@@ -817,77 +826,37 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
 					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
 				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 			}
 		} else if chartType == 2 && item.IsAxis == 0 {
-			// 右轴数据处理
-			xStartDate := "01-01"
-
-			jumpYear := 0
-			var seasonExtra data_manage.SeasonExtraItem
-			if seasonExtraConfig != "" {
-				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
-				if err != nil {
-					return
-				}
-			}
-
-			if seasonExtra.XStartDate != "" {
-				xStartDate = seasonExtra.XStartDate
-				jumpYear = seasonExtra.JumpYear
-			}
-
-			length := len(dataList)
-			if length == 0 {
-				return
-			}
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = dataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
-
-			var rightAxisDate time.Time
-			if jumpYear == 1 {
-				latestDate = latestDate.AddDate(-1, 0, 0)
-			}
-			latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
-			rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
-			if err != nil {
-				return
-			}
-
-			nowYear := time.Now().Year()
 			newDataList := make([]*data_manage.EdbDataList, 0)
+			seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+			seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
 				if e != nil {
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
-				year := dataTimeT.Year()
-				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
-				v.DataTimestamp = newItemDate.UnixNano() / 1e6
-				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}
@@ -904,7 +873,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 }
 
 // GetSeasonEdbInfoDataListByXDate 季节性图的指标数据根据横轴展示
-func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort data_manage.QuarterDataList, err error) {
+func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort data_manage.QuarterDataList, xStartDateWithYear string, xEndDateWithYear string, err error) {
 	xStartDate := "01-01"
 	xEndDate := "12-31"
 	jumpYear := 0
@@ -954,7 +923,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latest
 		return
 	}
 	endYear := lastDateT.Year()
-	nowYear := time.Now().Year()
+	nowYear := endYear
 	chartLegendMaxYear := 0
 	dataMap := make(map[string]data_manage.QuarterXDateItem, 0)
 
@@ -1005,6 +974,8 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latest
 		dataMap[name] = item
 		chartLegendMap[name] = idx
 		idx++
+		xStartDateWithYear = startStr
+		xEndDateWithYear = endStr
 		fmt.Println("年份" + showName + "日期" + startStr + " " + endStr)
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
@@ -1101,7 +1072,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latest
 }
 
 // GetSeasonEdbInfoDataListByXDateNong 季节性图的指标数据根据横轴选择农历时展示
-func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort data_manage.QuarterDataList, err error) {
+func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort data_manage.QuarterDataList,  xStartDateWithYear string, xEndDateWithYear string, err error) {
 	xStartDate := "01-01"
 	xEndDate := "12-31"
 	jumpYear := 0
@@ -1153,7 +1124,7 @@ func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, late
 		return
 	}
 	endYear := lastDateT.Year()
-	nowYear := time.Now().Year()
+	nowYear := endYear
 	chartLegendMaxYear := 0
 	dataMap := make(map[string]data_manage.QuarterXDateItem, 0)
 
@@ -1206,6 +1177,8 @@ func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, late
 		endTmpT = endT
 		chartLegendMap[showName] = idx
 		idx++
+		xStartDateWithYear = startStr
+		xEndDateWithYear = endStr
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
 			break
@@ -3010,7 +2983,7 @@ func GetEdbSourceByEdbInfoIdList(chartEdbInfoMappingList []*data_manage.ChartEdb
 	}
 
 	for source, sourceName := range sourceMap {
-		if utils.InArrayByInt([]int{utils.DATA_SOURCE_MANUAL, utils.DATA_SOURCE_MYSTEEL_CHEMICAL}, source) {
+		if utils.InArrayByInt([]int{utils.DATA_SOURCE_MANUAL}, source) {
 			continue
 		}
 		sourceNameList = append(sourceNameList, sourceName)
@@ -3278,6 +3251,15 @@ func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, end
 func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string, isAxis int) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	// 如果是季节性图则进入特殊排序
+	if chartType == 2 {
+		// 根据设置的左右轴,对mappingList进行排序,1左轴排在前面,0右轴排在前面
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
@@ -3469,41 +3451,39 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
 					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
 				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 			}
 		} else if chartType == 2 && isAxis == 0 {
 			// 右轴数据处理,只要最新一年
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = dataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
 			newDataList := make([]*data_manage.EdbDataList, 0)
+			seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+			seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
 				if e != nil {
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				if dataTime.Year() == latestDate.Year() {
+				// 如果数据时间在横轴的开始日期和结束日期之间,则加入到数据列表中
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}

+ 19 - 50
services/data/chart_info_excel_balance.go

@@ -210,7 +210,15 @@ func GetBalanceExcelChartDetail(chartInfo *data_manage.ChartInfoView, mappingLis
 func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string, dataListMap map[int][]*data_manage.EdbDataList) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
-
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	// 如果是季节性图则进入特殊排序
+	if chartType == 2 {
+		// 根据设置的左右轴,对mappingList进行排序,1左轴排在前面,0右轴排在前面
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
 		item := new(data_manage.ChartEdbInfoMapping)
@@ -401,77 +409,38 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
 					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
 				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 			}
 		} else if chartType == 2 && item.IsAxis == 0 {
-			// 右轴数据处理
-			xStartDate := "01-01"
-
-			jumpYear := 0
-			var seasonExtra data_manage.SeasonExtraItem
-			if seasonExtraConfig != "" {
-				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
-				if err != nil {
-					return
-				}
-			}
-
-			if seasonExtra.XStartDate != "" {
-				xStartDate = seasonExtra.XStartDate
-				jumpYear = seasonExtra.JumpYear
-			}
-
-			length := len(dataList)
-			if length == 0 {
-				return
-			}
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = dataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
-
-			var rightAxisDate time.Time
-			if jumpYear == 1 {
-				latestDate = latestDate.AddDate(-1, 0, 0)
-				latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
-				rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
-				if err != nil {
-					return
-				}
-			}
-
-			nowYear := time.Now().Year()
 			newDataList := make([]*data_manage.EdbDataList, 0)
+			seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+			seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
 				if e != nil {
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
-				year := dataTimeT.Year()
-				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
-				v.DataTimestamp = newItemDate.UnixNano() / 1e6
-				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				// 如果数据时间在横轴的开始日期和结束日期之间,则加入到数据列表中
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}

+ 1 - 1
services/data/chart_theme.go

@@ -255,7 +255,7 @@ func getThemePreviewEdbDataMapList(chartType int, calendar, startDate, endDate s
 				continue
 			}
 
-			quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+			quarterDataList, _, _, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 			if tErr != nil {
 				err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 				return

+ 14 - 14
services/data/data_manage_permission/data_move.go

@@ -49,7 +49,7 @@ func GetEdbChartClassifyList(source, subSource int) (resp data_manage.EdbChartCl
 			resp.List = append(resp.List, &item)
 		}
 
-	case 2: //钢联化工数据库
+	case 2: //上海钢联数据库
 		rootList, e := data_manage.GetBaseFromMysteelChemicalClassifyByParentId(0)
 		if e != nil && e.Error() != utils.ErrNoRow() {
 			err = e
@@ -207,7 +207,7 @@ func GetExcelMenuTreeRecursive(list []*excel.ExcelClassifyItems, parentId int) [
 }
 
 // GetMoveEdbChartList 获取待转移的指标/图表列表
-// @param source 来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param source 来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 func GetMoveEdbChartList(source, subSource, userId int, keyword, classify string, startSize, pageSize int) (list []data_manage.MoveEdbChartList, total int, err error) {
 	var condition string
 	var pars []interface{}
@@ -265,7 +265,7 @@ func GetMoveEdbChartList(source, subSource, userId int, keyword, classify string
 			}
 		}
 
-	case 2: //钢联化工数据库
+	case 2: //上海钢联数据库
 		if keyword != `` {
 			condition += " AND (index_name like ? OR index_code like ? OR sys_user_real_name like ? ) "
 			pars = utils.GetLikeKeywordPars(pars, keyword, 3)
@@ -482,7 +482,7 @@ func GetMoveEdbChartList(source, subSource, userId int, keyword, classify string
 }
 
 // MoveEdbChart 转移指标/图表创建人
-// @param source 来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param source 来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 func MoveEdbChart(source, subSource, oldUserId, newUserId int, isSelectAll bool, dataId, noDataId []string, keyword, classify string, opUserId int) (err error, errMsg string) {
 	adminInfo, err := system.GetSysAdminById(newUserId)
 	if err != nil {
@@ -555,8 +555,8 @@ func MoveEdbChart(source, subSource, oldUserId, newUserId int, isSelectAll bool,
 			err = models.ModifyEdbinfoUserIdByCodeList(dataId, newUserId)
 		}
 
-	case 2: //钢联化工数据库
-		content += `(钢联化工数据库)`
+	case 2: //上海钢联数据库
+		content += `(上海钢联数据库)`
 		tmpList, tmpErr := data_manage.GetMysteelChemicalIndexListByIndexId(dataId)
 		if tmpErr != nil {
 			err = tmpErr
@@ -753,7 +753,7 @@ func MoveEdbChart(source, subSource, oldUserId, newUserId int, isSelectAll bool,
 // @Description: 通过原创建人转移指标/图表创建人
 // @author: Roc
 // @datetime 2024-03-26 15:11:12
-// @param sourceList []int 1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param sourceList []int 1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 // @param oldUserId []int
 // @param userId int
 // @param opUserId int
@@ -803,8 +803,8 @@ func MoveAllEdbChartOld(sourceList, oldUserIdList []int, userId, opUserId int) (
 				err = models.ModifyEdbinfoUserIdByOldUserId(oldUserIdList, userId)
 			}
 
-		case 2: //钢联化工数据库
-			sourceStrList = append(sourceStrList, "钢联化工数据库")
+		case 2: //上海钢联数据库
+			sourceStrList = append(sourceStrList, "上海钢联数据库")
 			tmpList, tmpErr := data_manage.GetMysteelChemicalIndexListByUserId(oldUserIdList)
 			if tmpErr != nil {
 				err = tmpErr
@@ -1005,7 +1005,7 @@ func MoveAllEdbChartOld(sourceList, oldUserIdList []int, userId, opUserId int) (
 // @Description: 通过原创建人转移指标/图表创建人
 // @author: Roc
 // @datetime 2024-03-26 15:11:12
-// @param sourceList []int 1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param sourceList []int 1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 // @param oldUserId []int
 // @param userId int
 // @param opUserId int
@@ -1026,7 +1026,7 @@ func MoveAllEdbChart(sourceList, oldUserIdList []int, userId, opUserId int) (err
 
 	var isMoveManual, isMoveMysteelChemical, isMoveEdb, isMovePredictEdb, isMoveChart, isMoveExcel bool
 
-	// 遍历需要转移的模块,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格,并找出当前需要转移的资产
+	// 遍历需要转移的模块,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格,并找出当前需要转移的资产
 	for _, source := range sourceList {
 		switch source {
 		case 1: //手工数据指标
@@ -1057,8 +1057,8 @@ func MoveAllEdbChart(sourceList, oldUserIdList []int, userId, opUserId int) (err
 				isMoveManual = true
 			}
 
-		case 2: //钢联化工数据库
-			sourceStrList = append(sourceStrList, "钢联化工数据库")
+		case 2: //上海钢联数据库
+			sourceStrList = append(sourceStrList, "上海钢联数据库")
 			tmpList, tmpErr := data_manage.GetMysteelChemicalIndexListByUserId(oldUserIdList)
 			if tmpErr != nil {
 				err = tmpErr
@@ -1275,7 +1275,7 @@ func GetMoveEdbChartCount(userId, countType int) (sourceMap map[int]int, err err
 	}
 
 	{
-		// 钢联化工数据库
+		// 上海钢联数据库
 		var condition string
 		var pars []interface{}
 		if userId > 0 {

+ 1 - 1
services/data/data_manage_permission/message.go

@@ -7,7 +7,7 @@ import (
 
 type MessageDetailItem struct {
 	DataPermissionMoveRecordId int64  ` orm:"column(data_permission_move_record_id);pk"` // 数据操作记录id
-	Source                     int32  // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                     int32  // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                  int32  // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode               string // 操作的唯一编码,主要是记录统一操作的日志
 	DataId                     string // 资产id(指标、图表、表格)

+ 60 - 8
services/data/edb_info.go

@@ -1810,7 +1810,7 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 		utils.DATA_SOURCE_MANUAL:              "手工数据",
 		utils.DATA_SOURCE_LZ:                  "隆众",
 		utils.DATA_SOURCE_YS:                  "SMM",
-		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_GL:                  "上海钢联",
 		utils.DATA_SOURCE_ZZ:                  "郑商所",
 		utils.DATA_SOURCE_DL:                  "大商所",
 		utils.DATA_SOURCE_SH:                  "上期所",
@@ -1820,7 +1820,7 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 		utils.DATA_SOURCE_LT:                  "路透",
 		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
 		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
-		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "上海钢联",
 		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
 		utils.DATA_SOURCE_COM_TRADE:           "UN",
 		utils.DATA_SOURCE_SCI:                 "SCI",
@@ -2459,7 +2459,7 @@ func determineDateRange(index, totalLength int, formulas []map[string]string) st
 	return ""
 }
 // GetEdbChartAdminList
-// @param source 来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param source 来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 func GetEdbChartAdminList(source int) (list []int, err error) {
 	switch source {
 	case 1: //手工数据指标
@@ -2468,7 +2468,7 @@ func GetEdbChartAdminList(source int) (list []int, err error) {
 			return
 		}
 
-	case 2: //钢联化工数据库
+	case 2: //上海钢联数据库
 		list, err = data_manage.GetMysteelChemicalIndexAdminList()
 		if err != nil {
 			return
@@ -2582,7 +2582,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 		utils.DATA_SOURCE_MANUAL:              "手工数据",
 		utils.DATA_SOURCE_LZ:                  "隆众",
 		utils.DATA_SOURCE_YS:                  "SMM",
-		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_GL:                  "上海钢联",
 		utils.DATA_SOURCE_ZZ:                  "郑商所",
 		utils.DATA_SOURCE_DL:                  "大商所",
 		utils.DATA_SOURCE_SH:                  "上期所",
@@ -2592,7 +2592,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 		utils.DATA_SOURCE_LT:                  "路透",
 		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
 		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
-		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "上海钢联",
 		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
 		utils.DATA_SOURCE_COM_TRADE:           "UN",
 		utils.DATA_SOURCE_SCI:                 "SCI",
@@ -2775,7 +2775,7 @@ func EdbInfoSmmApiAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo,
 		utils.DATA_SOURCE_MANUAL:              "手工数据",
 		utils.DATA_SOURCE_LZ:                  "隆众",
 		utils.DATA_SOURCE_YS:                  "SMM",
-		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_GL:                  "上海钢联",
 		utils.DATA_SOURCE_ZZ:                  "郑商所",
 		utils.DATA_SOURCE_DL:                  "大商所",
 		utils.DATA_SOURCE_SH:                  "上期所",
@@ -2785,7 +2785,7 @@ func EdbInfoSmmApiAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo,
 		utils.DATA_SOURCE_LT:                  "路透",
 		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
 		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
-		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "上海钢联",
 		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
 		utils.DATA_SOURCE_COM_TRADE:           "UN",
 		utils.DATA_SOURCE_SCI:                 "SCI",
@@ -2984,3 +2984,55 @@ func GetEdbTerminalCodeBySource(source int, edbCode, stockCode string) (terminal
 	}
 	return
 }
+
+// CheckWindThsDsParams wind同花顺日期序列参数校验
+func CheckWindThsDsParams(source, subSource int, period, days, priceAdj string) (ok bool, tips string) {
+	if source != utils.DATA_SOURCE_WIND && source != utils.DATA_SOURCE_THS {
+		ok = true
+		return
+	}
+	if subSource != utils.DATA_SUB_SOURCE_DATE {
+		ok = true
+		return
+	}
+
+	if source == utils.DATA_SOURCE_WIND {
+		periodArr := []string{
+			utils.WindWsdPeriodDefault, utils.WindWsdPeriodWeek, utils.WindWsdPeriodMonth,
+			utils.WindWsdPeriodQuarter, utils.WindWsdPeriodYear,
+		}
+		if period != "" && !utils.InArrayByStr(periodArr, period) {
+			tips = "周期类型有误"
+			return
+		}
+		daysArr := []string{utils.WindWsdDaysWeekdays, utils.WindWsdDaysDefault, utils.WindWsdDaysAlldays}
+		if days != "" && !utils.InArrayByStr(daysArr, days) {
+			tips = "日期类型有误"
+			return
+		}
+		priceAdjArr := []string{utils.WindWsdRestorationFront, utils.WindWsdRestorationBack, utils.WindWsdRestorationFixed}
+		if priceAdj != "" && !utils.InArrayByStr(priceAdjArr, priceAdj) {
+			tips = "复权方式有误"
+			return
+		}
+	}
+
+	if source == utils.DATA_SOURCE_THS {
+		periodArr := []string{
+			utils.WindWsdPeriodDefault, utils.WindWsdPeriodWeek, utils.WindWsdPeriodMonth,
+			utils.WindWsdPeriodQuarter, utils.ThsDsdPeriodHalfYear, utils.WindWsdPeriodYear,
+		}
+		if period != "" && !utils.InArrayByStr(periodArr, period) {
+			tips = "周期类型有误"
+			return
+		}
+		daysArr := []string{utils.ThsDsDaysDefault, utils.ThsDsDaysAlldays}
+		if days != "" && !utils.InArrayByStr(daysArr, days) {
+			tips = "日期类型有误"
+			return
+		}
+	}
+
+	ok = true
+	return
+}

+ 157 - 0
services/dongwu_sms.go

@@ -0,0 +1,157 @@
+package services
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"strings"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/utils"
+)
+
+type DongWuSms struct{}
+
+func (cli *DongWuSms) SendUserLoginCode(req UserLoginSmsCodeReq) (result UserLoginSmsCodeResult, err error) {
+	if req.Mobile == "" || req.VerifyCode == "" {
+		err = fmt.Errorf("参数有误, Mobile: %s, VerifyCode: %s", req.Mobile, req.VerifyCode)
+		return
+	}
+	// Get configuration
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	tpl := confMap[models.BusinessConfLoginSmsTplContent]
+	if tpl == "" {
+		err = fmt.Errorf("请先配置短信模板")
+		return
+	}
+	
+	appKey := confMap[models.BusinessConfDongwuSmsAppKey]
+	if appKey == "" {
+		err = fmt.Errorf("请先配置东吴短信AppKey")
+		return
+	}
+	
+
+	// Call DongWu SMS API
+	apiURL := confMap[models.BusinessConfDongwuSmsApiUrl]
+	if apiURL == "" {
+		err = fmt.Errorf("请先配置东吴短信API地址")
+		return
+	}
+
+	// 短信内容
+	smsContent := strings.Replace(tpl, "{{VERIFY_CODE}}", req.VerifyCode, 1)
+	smsContent = strings.Replace(smsContent, "{{EXPIRED_MINUTE}}", strconv.Itoa(utils.VerifyCodeExpireMinute), 1)
+	apiResp, err := cli.sendUserLoginCode(req.Mobile, apiURL, appKey, smsContent)
+	if err != nil {
+		return
+	}
+	if apiResp.Status == "success" {
+		if apiResp.Result != "" {
+			var resultDetail DongWuSmsResultDetail
+			if e := json.Unmarshal([]byte(apiResp.Result), &resultDetail); e != nil {
+				err = fmt.Errorf("parse result detail err: %s", e.Error())
+				return
+			}
+			if resultDetail.Result == "0" {
+				result.Success = true
+				result.Message = resultDetail.Desc
+			} else {
+				result.Success = false
+				result.Message = fmt.Sprintf("发送失败, 错误码: %s, 错误信息: %s", resultDetail.Result, resultDetail.Desc)
+			}
+		}
+		return
+	} else {
+		result.Success = false
+		result.Message = apiResp.Message
+	}
+
+	return
+}
+
+type DongWuSmsApiParams struct {
+	GeneralAccount string `json:"generalAccount"`
+	Receivers string `json:"receivers"`
+	Text string `json:"text"`
+	
+	SubCode string `json:"subCode"`
+	SmsId string `json:"smsId"` //短信编号	(32 位 UUID),需保证唯一,选填
+	SendTime string `json:"sendTime"` // 选填,定时发送时间,格式 yyyyMMddHHmm,为空或早于当前时间则 立即发送;
+	Sign string `json:"sign"` // 选填,短信签名,为空时使用默认签名,默认为【东吴期货】
+}
+
+type DongWuSmsApiResult struct {
+	Status    string    `json:"status"`
+	Message string `json:"message"`
+	SmsType string `json:"smsType"` //短信平台类型,仅当status="success"时不为空,"ssdj"代表三三得九平台,"dh3t"代表大汉三通平台
+	Result    string    `json:"result"`
+}
+
+type DongWuSmsResultDetail struct {
+	Msgid string `json:"msgid"`
+	Result string `json:"result"`
+	Desc string `json:"desc"`
+	FailPhones string `json:"failPhones"`
+}
+
+func (cli *DongWuSms) sendUserLoginCode(mobile, apiURL, appKey, content string) (apiResp *DongWuSmsApiResult, err error) {
+	// Prepare request parameters
+	params := DongWuSmsApiParams{
+		GeneralAccount:  appKey,
+		Receivers:     mobile,
+		Text:   content,
+	}
+
+	// Make HTTP request
+	jsonData, e := json.Marshal(params)
+	if e != nil {
+		err = fmt.Errorf("json marshal err: %s", e.Error())
+		return
+	}
+
+	httpReq, e := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
+	if e != nil {
+		err = fmt.Errorf("create request err: %s", e.Error())
+		return
+	}
+
+	httpReq.Header.Set("Content-Type", "application/json")
+	client := &http.Client{}
+	resp, e := client.Do(httpReq)
+	if e != nil {
+		err = fmt.Errorf("send request err: %s", e.Error())
+		return
+	}
+	defer resp.Body.Close()
+
+	body, e := ioutil.ReadAll(resp.Body)
+	if e != nil {
+		err = fmt.Errorf("read response err: %s", e.Error())
+		return
+	}
+	apiResp = &DongWuSmsApiResult{}
+	if e = json.Unmarshal(body, &apiResp); e != nil {
+		err = fmt.Errorf("parse response err: %s", e.Error())
+		return
+	}
+	// Parse the result string if status is success
+	if apiResp.Status == "success" && apiResp.Result != "" {
+		var resultDetail DongWuSmsResultDetail
+		if e := json.Unmarshal([]byte(apiResp.Result), &resultDetail); e != nil {
+			err = fmt.Errorf("parse result detail err: %s", e.Error())
+			return
+		}
+		// Update the response handling in SendUserLoginCode
+		if resultDetail.Result == "0" {
+			return
+		}
+	}
+	return
+}

+ 1 - 0
services/elastic.go

@@ -102,6 +102,7 @@ func EsAddOrEditReport(indexName, docId string, item *models.ElasticReportDetail
 			"BodyContent":        item.BodyContent,
 			"PublishTime":        item.PublishTime,
 			"PublishState":       item.PublishState,
+			"IsPublicPublish":    item.IsPublicPublish,
 			"Author":             item.Author,
 			"ClassifyIdFirst":    item.ClassifyIdFirst,
 			"ClassifyNameFirst":  item.ClassifyNameFirst,

+ 42 - 0
services/english_report.go

@@ -964,3 +964,45 @@ func UpdateEnglishCompanyEnabledByCompanyId(companyId int) (err error) {
 	}
 	return
 }
+
+// GetEnglishReportToken
+// @Description: 获取token
+// @author: Roc
+// @datetime 2025-03-18 10:35:11
+// @param reportId int
+// @param reportCode string
+// @return token string
+// @return err error
+func GetEnglishReportToken(reportId int, reportCode string) (token string, err error) {
+	// 图表授权token
+	token = utils.MD5(fmt.Sprint(reportCode, time.Now().UnixNano()/1e6))
+	err = generalReportAuthToken(token, `en:`, reportId)
+
+	return
+}
+
+func GetGeneralEnglishReportPdfUrl(reportId int, reportCode string) (pdfUrl string) {
+	// 优先取Report2ImgUrl(用于兼容内外网环境的), 没有的话取报告详情地址
+	var reportUrl string
+	conf, _ := models.GetBusinessConfByKey(models.BusinessConfReport2ImgUrl)
+	if conf != nil && conf.ConfVal != "" {
+		reportUrl = conf.ConfVal
+	}
+	if reportUrl == "" {
+		conf, e := models.GetBusinessConfByKey(models.BusinessConfReportViewUrl)
+		if e != nil {
+			return
+		}
+		reportUrl = conf.ConfVal
+	}
+
+	token := utils.MD5(fmt.Sprint(pdfUrl, time.Now().UnixNano()/1e6))
+	e := generalReportAuthToken(token, `en:`, reportId)
+	if e == nil {
+		pdfUrl = fmt.Sprintf("%s&authToken=%s", pdfUrl, token)
+	}
+
+	pdfUrl = fmt.Sprintf("%s/reportshare_pdf_en?code=%s&authToken=%s", reportUrl, reportCode, token)
+
+	return
+}

+ 113 - 0
services/eta_forum/chart_classify.go

@@ -0,0 +1,113 @@
+package eta_forum
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/utils"
+	"fmt"
+)
+
+// ChartClassifySave 上传图表分类信息
+func ChartClassifySave(chartClassifyId int) (err error) {
+	if utils.BusinessCode == "" || (utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeDebug && utils.BusinessCode != utils.BusinessCodeSandbox) {
+		return
+	}
+	//查询分类信息
+	chartClassifyInfo, err := data_manage.GetChartClassifyById(chartClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = fmt.Errorf("分类不存在")
+			return
+		}
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if chartClassifyInfo.IsSelected != utils.ChartClassifyIsSelected {
+		return
+	}
+
+	reqJson, err := json.Marshal(chartClassifyInfo) 
+	if err != nil {
+		err = fmt.Errorf("参数解析异常,Err:" + err.Error())
+		return
+	}
+	respItem, err := ChartClassifySaveLib(string(reqJson))
+	if err != nil {
+		err = fmt.Errorf("上传图表分类信息失败,Err:" + err.Error())
+		return
+	}
+	if respItem.Ret != 200 {
+		err = fmt.Errorf("上传图表分类信息失败,Err:%v,errMsg:%v", respItem.Msg, respItem.ErrMsg)
+		return
+	}
+	return
+}
+
+type ChartClassifySaveBatchReq struct {
+	List []*data_manage.ChartClassify
+}
+
+//批量上传图表分类信息
+func ChartClassifySaveBatch(source int) (err error) {
+	if utils.BusinessCode == "" || (utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeDebug && utils.BusinessCode != utils.BusinessCodeSandbox) {
+		return
+	}
+
+	//查询分类信息
+	chartClassifyList, err := data_manage.GetChartClassifyInfoSelectedBySource(source)
+	if err != nil {
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	req := ChartClassifySaveBatchReq{
+		List: chartClassifyList,
+	}
+	reqJson, err := json.Marshal(req)
+	if err != nil {
+		err = fmt.Errorf("参数解析异常,Err:" + err.Error())
+		return
+	}
+	respItem, err := ChartClassifySaveBatchLib(string(reqJson))
+	if err != nil {
+		err = fmt.Errorf("上传图表分类信息失败,Err:" + err.Error())
+		return
+	}	
+	if respItem.Ret != 200 {
+		err = fmt.Errorf("上传图表分类信息失败,Err:%v,errMsg:%v", respItem.Msg, respItem.ErrMsg)
+		return
+	}
+	return
+}
+
+
+type DeleteChartReq struct {
+	ChartInfoId int `description:"图表id"`
+}
+
+// DeleteChart 上传图表接口
+func DeleteChartByForumChartInfoId(forumChartInfoId int) (err error, errMsg string) {
+	// 查询图表信息
+	req := new(DeleteChartReq)
+	req.ChartInfoId = forumChartInfoId
+
+	// 添加计算指标
+	reqJson, err := json.Marshal(req)
+	if err != nil {
+		errMsg = "参数解析异常"
+		err = fmt.Errorf("参数解析异常,Err:" + err.Error())
+		return
+	}
+	respItem, err := ChartDeleteLib(string(reqJson))
+	if err != nil {
+		errMsg = "撤回失败"
+		err = fmt.Errorf("撤回失败,Err:" + err.Error())
+		return
+	}
+	if respItem.Ret != 200 {
+		errMsg = "撤回失败"
+		err = fmt.Errorf(respItem.ErrMsg)
+		return
+	}
+
+	return
+}

+ 71 - 0
services/eta_forum/eta_forum_hub_lib.go

@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"strings"
 )
 
 // GetUserChartListLib 查询有权限的图表列表
@@ -49,6 +50,34 @@ func getChartFromUniqueCodeLib(req string) (resp ChartFromUniqueCodeResp, err er
 	return
 }
 
+// ChartClassifySaveLib 上传图表分类信息
+func ChartClassifySaveLib(req string) (resp *models.BaseResponse, err error) {
+	_, resultByte, err := post(req, "/v1/chart_classify/save")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// ChartClassifySaveBatchLib 批量上传图表分类信息
+func ChartClassifySaveBatchLib(req string) (resp *models.BaseResponse, err error) {
+	_, resultByte, err := post(req, "/v1/chart_classify/batch_save")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+// ChartDeleteLib 从社区撤回图表
+func ChartDeleteLib(req string) (resp *models.BaseResponse, err error) {
+	_, resultByte, err := post(req, "/v1/chart/delete")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
 // get
 func get(paramStr string, urlStr string) (resp *models.BaseResponse, result []byte, err error) {
 	if utils.ETA_FORUM_HUB_URL == "" {
@@ -86,3 +115,45 @@ func HttpGet(url string) ([]byte, error) {
 	utils.FileLog.Debug("HttpPost:" + string(b))
 	return b, err
 }
+
+func post(paramStr string, urlStr string) (resp *models.BaseResponse, result []byte, err error) {
+	if utils.ETA_FORUM_HUB_URL == "" {
+		err = fmt.Errorf("ETA社区桥接服务地址为空")
+		return
+	}
+	postUrl := utils.ETA_FORUM_HUB_URL + urlStr
+	result, err = HttpPost(postUrl, paramStr, "application/json")
+	if err != nil {
+		err = fmt.Errorf("调用ETA社区桥接服务接口失败 error:%s", err.Error())
+		return
+	}
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func HttpPost(url, postData string, params ...string) ([]byte, error) {
+	body := ioutil.NopCloser(strings.NewReader(postData))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	contentType := "application/x-www-form-urlencoded;charset=utf-8"
+	if len(params) > 0 && params[0] != "" {
+		contentType = params[0]
+	}
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("authorization", utils.MD5(utils.ETA_FORUM_HUB_NAME_EN+utils.ETA_FORUM_HUB_MD5_KEY))
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	b, err := ioutil.ReadAll(resp.Body)
+	utils.FileLog.Debug("HttpPost:" + string(b))
+	return b, err
+}

+ 6 - 2
services/material/material.go

@@ -559,9 +559,13 @@ func GetBatchSelectedMaterialList(classifyId int, keyword string, isShowMe bool,
 	if keyword != "" {
 		switch lang {
 		case utils.LANG_EN:
-			condition += ` AND  ( material_name_en LIKE '%` + keyword + `%' )`
+			likeKey := `%` + keyword + `%`
+			condition += ` AND  ( material_name_en LIKE ? )`
+			pars = append(pars, likeKey)
 		default:
-			condition += ` AND  ( material_name LIKE '%` + keyword + `%' )`
+			likeKey := `%` + keyword + `%`
+			condition += ` AND  ( material_name LIKE ? )`
+			pars = append(pars, likeKey)
 		}
 	}
 

+ 74 - 47
services/ppt/ppt_english_group.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_mobile/models/system"
 	"eta/eta_mobile/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
 	"sort"
 	"strconv"
 	"strings"
@@ -60,11 +61,23 @@ func AddGroupPptEnglishMapping(pptId int64, groupId int64, adminId int, adminRea
 		err = errors.New("目录查询出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := ppt_english.GetMaxSortByEnglishGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	bindInfo := &ppt_english.PptEnglishGroupMapping{
 		GroupId:       groupId,
 		PptId:         pptId,
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		PptSort:       newSort,
+		CreateTime:    time.Now(),
+		ModifyTime:    time.Now(),
 	}
 
 	newId, err = ppt_english.AddPptGroupMapping(bindInfo)
@@ -74,12 +87,13 @@ func AddGroupPptEnglishMapping(pptId int64, groupId int64, adminId int, adminRea
 	}
 
 	bindInfo.GroupPptId = newId
-	bindInfo.PptSort = newId
-	err = bindInfo.Update([]string{"ppt_sort"})
-	if err != nil {
-		err = errors.New("更新排序失败:" + err.Error())
-		return
-	}
+	//bindInfo.PptSort = newId
+	//err = bindInfo.Update([]string{"ppt_sort"})
+	//if err != nil {
+	//	err = errors.New("更新排序失败:" + err.Error())
+	//	return
+	//}
+
 	return
 }
 
@@ -176,7 +190,7 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 		return
 	}
 	//查询该目录下面是否存在ppt,如果存在则批量复制ppt到新目录下
-	mappingList, err := ppt_english.GetPptMappingListByGroupId(groupId)
+	mappingList, err := ppt_english.GetPptMappingListByGroupIdDesc(groupId)
 	if err != nil {
 		err = errors.New("查询目录里的ppt列表出错:" + err.Error())
 		return
@@ -194,6 +208,11 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 		err = errors.New("查询ppt列表出错:" + err.Error())
 		return
 	}
+	pptMap := make(map[int]*ppt_english.PptEnglish)
+	for _, pptItem := range pptList {
+		pptMap[pptItem.PptId] = pptItem
+	}
+
 	pptNames, err := ppt_english.GetAllPptTitle()
 	if err != nil {
 		err = errors.New("查询ppt标题出错:" + err.Error())
@@ -201,23 +220,32 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 	}
 	//批量复制ppt,更新作者为当前账号,并返回新的pptID
 	newPptList := make([]*ppt_english.PptEnglish, 0)
-	for _, v := range pptList {
+	for _, mapping := range mappingList {
+		pptItem, ok := pptMap[int(mapping.PptId)]
+		// 不存在该ppt,那么就过滤
+		if !ok {
+			continue
+		}
+
 		tmp := &ppt_english.PptEnglish{
-			TemplateType:  v.TemplateType,
-			BackgroundImg: v.BackgroundImg,
-			Title:         generateCopyName(v.Title, 1, pptNames),
-			ReportType:    v.ReportType,
-			PptDate:       v.PptDate,
-			Content:       v.Content,
-			CoverContent:  v.CoverContent,
+			TemplateType:  pptItem.TemplateType,
+			BackgroundImg: pptItem.BackgroundImg,
+			Title:         generateCopyName(pptItem.Title, 1, pptNames),
+			ReportType:    pptItem.ReportType,
+			PptDate:       pptItem.PptDate,
+			Content:       pptItem.Content,
+			CoverContent:  pptItem.CoverContent,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       adminId,
 			AdminRealName: adminRealName,
+			TitleSetting:  pptItem.TitleSetting,
 		}
 		newPptList = append(newPptList, tmp)
 	}
-	if len(newPptList) > 0 {
+
+	pptNum := len(newPptList)
+	if pptNum > 0 {
 		err = ppt_english.AddPptEnglishMulti(newPptList)
 		if err != nil {
 			err = errors.New("复制目录里的ppt出错:" + err.Error())
@@ -225,12 +253,13 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 		}
 
 		newMappings := make([]*ppt_english.PptEnglishGroupMapping, 0)
-		for _, v := range newPptList {
+		for k, v := range newPptList {
 			tmp := &ppt_english.PptEnglishGroupMapping{
 				GroupId:       newGroupId,
 				PptId:         int64(v.PptId),
 				CreateTime:    time.Now(),
 				ModifyTime:    time.Now(),
+				PptSort:       float64(pptNum - k),
 				AdminId:       adminId,
 				AdminRealName: adminRealName,
 			}
@@ -242,15 +271,15 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 			return
 		}
 		//批量更新排序字段
-		var newGroupPptIds []int64
-		for _, v := range newMappings {
-			newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
-		}
-		err = ppt_english.UpdatePptGroupMappingSortMulti(newGroupPptIds)
-		if err != nil {
-			err = errors.New("更新排序标识出错:" + err.Error())
-			return
-		}
+		//var newGroupPptIds []int64
+		//for _, v := range newMappings {
+		//	newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
+		//}
+		//err = ppt_english.UpdatePptGroupMappingSortMulti(newGroupPptIds)
+		//if err != nil {
+		//	err = errors.New("更新排序标识出错:" + err.Error())
+		//	return
+		//}
 	}
 	return
 }
@@ -371,7 +400,7 @@ func GetGroupPptEnglishList(groupId int64, adminId int) (ret ppt_english.RespGro
 			return
 		}
 		groups = append(groups, groupInfo)
-		groupPptList, tErr = ppt_english.GetPptMappingListByGroupId(groupId)
+		groupPptList, tErr = ppt_english.GetPptMappingListByGroupIdDesc(groupId)
 		if tErr != nil {
 			err = errors.New("目录里的ppt查询出错:" + tErr.Error())
 			return
@@ -462,8 +491,6 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 		return
 	}
 	var updateStr []string
-	//如果更换了目录,默认当前排序值为0
-	var currentSort, prevSort, nextSort int64
 	//判断是否更换group
 	if groupPpt.GroupId != groupId {
 		_, err = ppt_english.GetPptGroupByGroupIdAdminId(groupId, adminId)
@@ -485,7 +512,6 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 		groupPpt.GroupId = groupId
 		updateStr = append(updateStr, "group_id")
 	}
-	currentSort = groupPpt.PptSort
 
 	var prevGroupPpt *ppt_english.PptEnglishGroupMapping
 	var nextGroupPpt *ppt_english.PptEnglishGroupMapping
@@ -499,7 +525,6 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		prevSort = prevGroupPpt.PptSort
 	}
 
 	if nextGroupPptId > 0 {
@@ -512,29 +537,31 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		nextSort = nextGroupPpt.PptSort
 	}
 
-	updateStr = append(updateStr, "ppt_sort")
-
-	//移到两个排序值中间操作
-	if prevSort >= currentSort {
-		//往下移动
-		err = ppt_english.MoveDownGroupPptBySort(groupId, prevSort, currentSort)
-		if err != nil {
-			err = errors.New("向下移动ppt出错:" + err.Error())
+	pptSort := groupPpt.PptSort
+	if prevGroupPpt != nil && prevGroupPpt.PptId > 0 && nextGroupPpt != nil && nextGroupPpt.PptId > 0 { // 两个之间
+		pptSort, _ = decimal.NewFromFloat(prevGroupPpt.PptSort).Add(decimal.NewFromFloat(nextGroupPpt.PptSort)).Div(decimal.NewFromInt(2)).Float64()
+	} else if prevGroupPpt != nil && prevGroupPpt.PptId > 0 {
+		// 最下面
+		maxSort, tmpErr := ppt_english.GetMinSortByEnglishGroupId(prevGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = prevSort
-	} else if nextSort <= currentSort && nextSort != 0 {
-		//往上移动
-		err = ppt_english.MoveUpGroupPptBySort(groupId, nextSort, currentSort)
-		if err != nil {
-			err = errors.New("向上移动ppt出错:" + err.Error())
+		pptSort, _ = decimal.NewFromFloat(maxSort).Sub(decimal.NewFromInt(1)).Float64()
+	} else if nextGroupPpt != nil && nextGroupPpt.PptId > 0 {
+		// 最上面
+		minSort, tmpErr := ppt_english.GetMaxSortByEnglishGroupId(nextGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = nextSort
+		pptSort, _ = decimal.NewFromFloat(minSort).Add(decimal.NewFromInt(1)).Float64()
 	}
+
+	groupPpt.PptSort = pptSort
+	updateStr = append(updateStr, "ppt_sort")
 	//更新当前排序
 	err = groupPpt.Update(updateStr)
 	if err != nil {

+ 89 - 185
services/ppt/ppt_group.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_mobile/models/system"
 	"eta/eta_mobile/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
 	"sort"
 	"strconv"
 	"strings"
@@ -224,11 +225,23 @@ func AddGroupPptMapping(pptId int64, groupId int64, adminId int, adminRealName s
 		err = errors.New("目录查询出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := models.GetMaxSortByGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	bindInfo := &models.PptV2GroupMapping{
 		GroupId:       groupId,
 		PptId:         pptId,
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		CreateTime:    time.Now(),
+		ModifyTime:    time.Now(),
+		PptSort:       newSort,
 	}
 
 	newId, err = models.AddPptGroupMapping(bindInfo)
@@ -238,12 +251,12 @@ func AddGroupPptMapping(pptId int64, groupId int64, adminId int, adminRealName s
 	}
 
 	bindInfo.GroupPptId = newId
-	bindInfo.PptSort = newId
-	err = bindInfo.Update([]string{"ppt_sort"})
-	if err != nil {
-		err = errors.New("更新排序失败:" + err.Error())
-		return
-	}
+	//bindInfo.PptSort = newId
+	//err = bindInfo.Update([]string{"ppt_sort"})
+	//if err != nil {
+	//	err = errors.New("更新排序失败:" + err.Error())
+	//	return
+	//}
 	return
 }
 
@@ -358,6 +371,11 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 		err = errors.New("查询ppt列表出错:" + err.Error())
 		return
 	}
+	pptMap := make(map[int]*models.PptV2)
+	for _, pptItem := range pptList {
+		pptMap[pptItem.PptId] = pptItem
+	}
+
 	pptNames, err := models.GetAllPptTitle()
 	if err != nil {
 		err = errors.New("查询ppt标题出错:" + err.Error())
@@ -365,29 +383,35 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 	}
 	//批量复制ppt,更新作者为当前账号,并返回新的pptID
 	newPptList := make([]*models.PptV2, 0)
-	for _, v := range pptList {
-		if v.PptVersion != 2 { //只复制新版的ppt,旧版本的ppt忽略
+	for _, mapping := range mappingList {
+		pptItem, ok := pptMap[int(mapping.PptId)]
+		// 不存在该ppt,那么就过滤
+		if !ok {
+			continue
+		}
+		if pptItem.PptVersion != 2 { //只复制新版的ppt,旧版本的ppt忽略
 			continue
 		}
 		tmp := &models.PptV2{
-			TemplateType:  v.TemplateType,
-			BackgroundImg: v.BackgroundImg,
-			Title:         generateCopyName(v.Title, 1, pptNames),
-			ReportType:    v.ReportType,
-			PptDate:       v.PptDate,
-			Content:       v.Content,
-			CoverContent:  v.CoverContent,
-			PptUrl:        v.PptUrl,
-			PptxUrl:       v.PptxUrl,
+			TemplateType:  pptItem.TemplateType,
+			BackgroundImg: pptItem.BackgroundImg,
+			Title:         generateCopyName(pptItem.Title, 1, pptNames),
+			ReportType:    pptItem.ReportType,
+			PptDate:       pptItem.PptDate,
+			Content:       pptItem.Content,
+			CoverContent:  pptItem.CoverContent,
+			PptUrl:        pptItem.PptUrl,
+			PptxUrl:       pptItem.PptxUrl,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       adminId,
 			AdminRealName: adminRealName,
-			PptVersion:    v.PptVersion,
+			PptVersion:    pptItem.PptVersion,
 		}
 		newPptList = append(newPptList, tmp)
 	}
-	if len(newPptList) > 0 {
+	pptNum := len(newPptList)
+	if pptNum > 0 {
 		err = models.AddPptV2Multi(newPptList)
 		if err != nil {
 			err = errors.New("复制目录里的ppt出错:" + err.Error())
@@ -395,7 +419,7 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 		}
 
 		newMappings := make([]*models.PptV2GroupMapping, 0)
-		for _, v := range newPptList {
+		for k, v := range newPptList {
 			tmp := &models.PptV2GroupMapping{
 				GroupId:       newGroupId,
 				PptId:         int64(v.PptId),
@@ -403,6 +427,7 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 				ModifyTime:    time.Now(),
 				AdminId:       adminId,
 				AdminRealName: adminRealName,
+				PptSort:       float64(pptNum - k),
 			}
 			newMappings = append(newMappings, tmp)
 		}
@@ -412,15 +437,15 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 			return
 		}
 		//批量更新排序字段
-		var newGroupPptIds []int64
-		for _, v := range newMappings {
-			newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
-		}
-		err = models.UpdatePptGroupMappingSortMulti(newGroupPptIds)
-		if err != nil {
-			err = errors.New("更新排序标识出错:" + err.Error())
-			return
-		}
+		//var newGroupPptIds []int64
+		//for _, v := range newMappings {
+		//	newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
+		//}
+		//err = models.UpdatePptGroupMappingSortMulti(newGroupPptIds)
+		//if err != nil {
+		//	err = errors.New("更新排序标识出错:" + err.Error())
+		//	return
+		//}
 	}
 	return
 }
@@ -651,8 +676,6 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 		return
 	}
 	var updateStr []string
-	//如果更换了目录,默认当前排序值为0
-	var currentSort, prevSort, nextSort int64
 	//判断是否更换group
 	if groupPpt.GroupId != groupId {
 		_, err = models.GetPptGroupByGroupIdAdminId(groupId, adminId)
@@ -674,7 +697,6 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 		groupPpt.GroupId = groupId
 		updateStr = append(updateStr, "group_id")
 	}
-	currentSort = groupPpt.PptSort
 
 	var prevGroupPpt *models.PptV2GroupMapping
 	var nextGroupPpt *models.PptV2GroupMapping
@@ -688,7 +710,6 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		prevSort = prevGroupPpt.PptSort
 	}
 
 	if nextGroupPptId > 0 {
@@ -701,29 +722,31 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		nextSort = nextGroupPpt.PptSort
 	}
 
-	updateStr = append(updateStr, "ppt_sort")
-
-	//移到两个排序值中间操作
-	if prevSort >= currentSort {
-		//往下移动
-		err = models.MoveDownGroupPptBySort(groupId, prevSort, currentSort)
-		if err != nil {
-			err = errors.New("向下移动ppt出错:" + err.Error())
+	pptSort := groupPpt.PptSort
+	if prevGroupPpt != nil && prevGroupPpt.PptId > 0 && nextGroupPpt != nil && nextGroupPpt.PptId > 0 { // 两个之间
+		pptSort, _ = decimal.NewFromFloat(prevGroupPpt.PptSort).Add(decimal.NewFromFloat(nextGroupPpt.PptSort)).Div(decimal.NewFromInt(2)).Float64()
+	} else if prevGroupPpt != nil && prevGroupPpt.PptId > 0 {
+		// 最下面
+		maxSort, tmpErr := models.GetMinSortByGroupId(prevGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = prevSort
-	} else if nextSort <= currentSort && nextSort != 0 {
-		//往上移动
-		err = models.MoveUpGroupPptBySort(groupId, nextSort, currentSort)
-		if err != nil {
-			err = errors.New("向上移动ppt出错:" + err.Error())
+		pptSort, _ = decimal.NewFromFloat(maxSort).Sub(decimal.NewFromInt(1)).Float64()
+	} else if nextGroupPpt != nil && nextGroupPpt.PptId > 0 {
+		// 最上面
+		minSort, tmpErr := models.GetMaxSortByGroupId(nextGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = nextSort
+		pptSort, _ = decimal.NewFromFloat(minSort).Add(decimal.NewFromInt(1)).Float64()
 	}
+
+	groupPpt.PptSort = pptSort
+	updateStr = append(updateStr, "ppt_sort")
 	//更新当前排序
 	err = groupPpt.Update(updateStr)
 	if err != nil {
@@ -941,6 +964,15 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		err = errors.New("复制目录里的ppt出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := models.GetMaxSortByGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	var newMappings []*models.PptV2GroupMapping
 	newGroupPpt := &models.PptV2GroupMapping{
 		GroupId:       groupId,
@@ -949,6 +981,7 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		ModifyTime:    time.Now(),
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		PptSort:       newSort,
 	}
 	newMappings = append(newMappings, newGroupPpt)
 
@@ -957,12 +990,12 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		err = errors.New("复制目录里的ppt出错:" + err.Error())
 		return
 	}
-	//批量更新排序字段
-	err = models.UpdatePptGroupMappingSortMulti([]int64{newGroupPpt.GroupPptId})
-	if err != nil {
-		err = errors.New("更新排序标识出错:" + err.Error())
-		return
-	}
+	////批量更新排序字段
+	//err = models.UpdatePptGroupMappingSortMulti([]int64{newGroupPpt.GroupPptId})
+	//if err != nil {
+	//	err = errors.New("更新排序标识出错:" + err.Error())
+	//	return
+	//}
 	pptPage := 0
 	// 因之前并没有存储PPT页数字段,所以此处读取PPT内容的长度
 	if newPpt.Content != "" {
@@ -1045,134 +1078,6 @@ func SearchGroupPpt(keyWord string) (ret models.RespSearchGroupPptList, err erro
 	return
 }
 
-// InitPptGroup 初始化目录分组
-func InitPptGroup() (err error) {
-	//查询所有的ppt
-	now := time.Now()
-	pptList, err := models.GetPptV2ByCondition("", []interface{}{})
-	if err != nil {
-		errors.New("查询所有的ppt出错" + err.Error())
-		return
-	}
-	pptAdminMap := make(map[int]string, 0)
-	adminPptListMap := make(map[int][]*models.PptV2)
-	oldPptAdminMap := make(map[int]string, 0)
-	oldAdminPptListMap := make(map[int][]*models.PptV2)
-	//查询所有的ppt作者
-	for _, v := range pptList {
-		if v.PptVersion == 2 {
-			if _, ok := pptAdminMap[v.AdminId]; !ok {
-				pptAdminMap[v.AdminId] = v.AdminRealName
-			}
-			adminPptListMap[v.AdminId] = append(adminPptListMap[v.AdminId], v)
-		} else {
-			if _, ok := oldPptAdminMap[v.AdminId]; !ok {
-				oldPptAdminMap[v.AdminId] = v.AdminRealName
-			}
-			oldAdminPptListMap[v.AdminId] = append(oldAdminPptListMap[v.AdminId], v)
-		}
-	}
-	//批量生成历史目录,共享目录
-	newOldGroupList := make([]*models.PptV2Group, 0)
-	//批量生成目录,共享目录
-	newGroupList := make([]*models.PptV2Group, 0)
-	if len(pptAdminMap) > 0 {
-		for k, v := range pptAdminMap {
-			tmp := &models.PptV2Group{
-				GroupName:  v + "的PPT",
-				AdminId:    k,
-				IsShare:    1,
-				CreateTime: now,
-				ModifyTime: now,
-				ShareTime:  now,
-			}
-			newGroupList = append(newGroupList, tmp)
-		}
-		//批量把对应的ppt放到目录当中
-		err = models.AddPptGroupMulti(newGroupList)
-		if err != nil {
-			err = errors.New("创建目录出错:" + err.Error())
-			return
-		}
-	}
-	if len(oldPptAdminMap) > 0 {
-		for k, v := range oldPptAdminMap {
-			tmp := &models.PptV2Group{
-				GroupName:  v + "的历史PPT",
-				AdminId:    k,
-				IsShare:    1,
-				CreateTime: now,
-				ModifyTime: now,
-				ShareTime:  now,
-			}
-			newOldGroupList = append(newOldGroupList, tmp)
-		}
-		//批量把对应的ppt放到目录当中
-		err = models.AddPptGroupMulti(newOldGroupList)
-		if err != nil {
-			err = errors.New("创建目录出错:" + err.Error())
-			return
-		}
-	}
-	//批量更新排序字段
-	var newGroupIds []int64
-	var newMappings []*models.PptV2GroupMapping
-	for _, v := range newGroupList {
-		newGroupIds = append(newGroupIds, v.GroupId)
-		if ppts, ok := adminPptListMap[v.AdminId]; ok {
-			for _, p := range ppts {
-				tmp := &models.PptV2GroupMapping{
-					GroupId:       v.GroupId,
-					PptId:         int64(p.PptId),
-					CreateTime:    now,
-					ModifyTime:    now,
-					AdminId:       p.AdminId,
-					AdminRealName: p.AdminRealName,
-				}
-				newMappings = append(newMappings, tmp)
-			}
-		}
-	}
-	for _, v := range newOldGroupList {
-		newGroupIds = append(newGroupIds, v.GroupId)
-		if oldPpts, ok1 := oldAdminPptListMap[v.AdminId]; ok1 {
-			for _, p := range oldPpts {
-				tmp := &models.PptV2GroupMapping{
-					GroupId:       v.GroupId,
-					PptId:         int64(p.PptId),
-					CreateTime:    now,
-					ModifyTime:    now,
-					AdminId:       p.AdminId,
-					AdminRealName: p.AdminRealName,
-				}
-				newMappings = append(newMappings, tmp)
-			}
-		}
-	}
-	err = models.UpdatePptGroupSortMulti(newGroupIds)
-	if err != nil {
-		err = errors.New("更新目录排序标识出错:" + err.Error())
-		return
-	}
-
-	err = models.AddPptGroupMappingMulti(newMappings)
-	if err != nil {
-		err = errors.New("创建目录里的ppt出错:" + err.Error())
-		return
-	}
-	//批量更新排序字段
-	var newGroupPptIds []int64
-	for _, v := range newMappings {
-		newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
-	}
-	err = models.UpdatePptGroupMappingSortMulti(newGroupPptIds)
-	if err != nil {
-		err = errors.New("更新排序标识出错:" + err.Error())
-		return
-	}
-	return
-}
-
 // GetGroupsByAdminIdV2 查询ppt目录列表
 // @Author roc
 // @Time 2022-08-29 15:22:20
@@ -1745,7 +1650,6 @@ func GetPptList(adminId int, keyword string) (ret models.RespGroupPptList, err e
 	return
 }
 
-
 // SearchPptList PPT搜索(我的/公开PPT)
 func SearchPptList(adminId int, keyword string) (ret models.RespGroupPptList, err error) {
 	list := make([]*models.RespGroupPptListItem, 0)
@@ -1791,4 +1695,4 @@ func SearchPptList(adminId int, keyword string) (ret models.RespGroupPptList, er
 	ret.List = list
 	ret.Total = len(list)
 	return
-}
+}

+ 5 - 4
services/report.go

@@ -143,7 +143,7 @@ func UpdateReportEs(reportId int, publishState int) (err error) {
 		if len(chapterList) > 0 {
 			// 更新章节的es数据
 			for _, chapterInfo := range chapterList {
-				err = updateReportChapterEsByChapter(chapterInfo)
+				err = updateReportChapterEsByChapter(chapterInfo, reportInfo.IsPublicPublish)
 				if err != nil {
 					return
 				}
@@ -241,7 +241,7 @@ func addCategoryAliasToArr(categoryArr []string) (aliasArr []string, err error)
 // @datetime 2024-06-20 13:16:22
 // @param reportChapterId int
 // @return err error
-func UpdateReportChapterEs(reportChapterId int) (err error) {
+func UpdateReportChapterEs(reportChapterId int, isPublicPublish int8) (err error) {
 	if reportChapterId <= 0 {
 		return
 	}
@@ -250,7 +250,7 @@ func UpdateReportChapterEs(reportChapterId int) (err error) {
 		return
 	}
 
-	err = updateReportChapterEsByChapter(chapterInfo)
+	err = updateReportChapterEsByChapter(chapterInfo, isPublicPublish)
 	if err != nil {
 		return
 	}
@@ -264,7 +264,7 @@ func UpdateReportChapterEs(reportChapterId int) (err error) {
 // @datetime 2024-06-20 13:16:11
 // @param chapterInfo *models.ReportChapter
 // @return err error
-func updateReportChapterEsByChapter(chapterInfo *models.ReportChapter) (err error) {
+func updateReportChapterEsByChapter(chapterInfo *models.ReportChapter, isPublicPublish int8) (err error) {
 	// 章节对应的品种
 	obj := report.ReportChapterPermissionMapping{}
 	permissionList, tmpErr := obj.GetPermissionItemListById(chapterInfo.ReportChapterId)
@@ -289,6 +289,7 @@ func updateReportChapterEsByChapter(chapterInfo *models.ReportChapter) (err erro
 		BodyContent:        utils.TrimHtml(html.UnescapeString(chapterInfo.Content)),
 		PublishTime:        chapterInfo.PublishTime.Format(utils.FormatDateTime),
 		PublishState:       chapterInfo.PublishState,
+		IsPublicPublish:    isPublicPublish,
 		Author:             chapterInfo.Author,
 		ClassifyIdFirst:    chapterInfo.ClassifyIdFirst,
 		ClassifyNameFirst:  chapterInfo.ClassifyNameFirst,

+ 5 - 1
services/report_approve.go

@@ -1,6 +1,7 @@
 package services
 
 import (
+	"eta/eta_mobile/cache"
 	"eta/eta_mobile/models"
 	"eta/eta_mobile/models/report_approve"
 	"eta/eta_mobile/models/smart_report"
@@ -841,13 +842,16 @@ func AfterReportApprovePass(reportType, reportId int) (err error) {
 
 		// 生成报告pdf和长图
 		{
-			reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ReportLayout)
+			reportPdfUrl := GetGeneralPdfUrl(reportInfo.Id, reportInfo.ReportCode, reportInfo.ReportLayout)
 			go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
 		}
 
 		//_ = CreateVideo(reportInfo)
 		_ = UpdateReportEs(reportInfo.Id, models.ReportStatePublished)
 
+		// 报告发布成功后,需要将相关信息入知识库
+		go cache.RagEtaReportOpToCache(reportInfo.Id, 0, `publish`)
+
 		return
 	}
 

+ 549 - 21
services/report_v2.go

@@ -2,7 +2,9 @@ package services
 
 import (
 	"archive/zip"
+	"encoding/json"
 	"errors"
+	"eta/eta_mobile/cache"
 	"eta/eta_mobile/models"
 	"eta/eta_mobile/models/report"
 	"eta/eta_mobile/models/report_approve"
@@ -11,10 +13,17 @@ import (
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/file"
 	"github.com/rdlucklib/rdluck_tools/http"
+	html2 "golang.org/x/net/html"
+	"net/url"
 	"os"
 	"path"
 	"strconv"
+	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context"
+	"github.com/go-redis/redis/v8"
 )
 
 // AddReportAndChapter
@@ -52,6 +61,8 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 
 	errMsg = "生成报告失败"
 
+	reportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+
 	// 报告继承
 	if inheritReportId > 0 {
 		inheritReport, tmpErr := models.GetReportByReportId(inheritReportId)
@@ -88,6 +99,25 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 				reportInfo.EndImg = inheritReport.EndImg
 			}
 			reportInfo.InheritReportId = inheritReport.Id
+			pages, tmpErr := report.GetReportFreeLayoutListByReportId(inheritReport.Id, 0)
+			if tmpErr != nil {
+				errMsg = "获取自由布局内容页失败"
+				err = tmpErr
+				return
+			}
+			for _, v := range pages {
+				reportFreeLayoutList = append(reportFreeLayoutList, &report.ReportFreeLayout{
+					Id:              0,
+					ReportId:        0,
+					ReportChapterId: 0,
+					Page:            v.Page,
+					IsChapter:       v.IsChapter,
+					Content:         v.Content,
+					ContentStruct:   v.ContentStruct,
+					CreateTime:      time.Now(),
+					ModifyTime:      time.Now(),
+				})
+			}
 		}
 	}
 
@@ -96,7 +126,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 
 	// 新增报告及章节
 	var reportId int64
-	reportId, err = models.AddReportAndChapter(reportInfo, allGrantUserList, addChapterList)
+	reportId, err = models.AddReportAndChapter(reportInfo, reportFreeLayoutList, allGrantUserList, addChapterList)
 	if err != nil {
 		err = errors.New("新增报告及章节失败, Err: " + err.Error())
 		return
@@ -126,6 +156,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 // @return errMsg string
 func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.Admin) (err error, errMsg string) {
 	errMsg = `保存失败`
+	oldIsPublicPublish := reportInfo.IsPublicPublish
 	//var stage int
 	//if reportInfo.ClassifyNameFirst != req.ClassifyNameFirst || reportInfo.ClassifyNameSecond != req.ClassifyNameSecond {
 	//	// 报告期数
@@ -186,7 +217,7 @@ func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.A
 	reportInfo.State = state
 
 	//updateCols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "ClassifyIdThird", "ClassifyNameThird", "Title", "Abstract", "Author", "Frequency", "Stage", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
-	updateCols := []string{"Title", "Abstract", "Author", "Frequency", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "State"}
+	updateCols := []string{"Title", "Abstract", "Author", "Frequency", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "State", "MiniShow", "FreeReportRatio"}
 
 	if req.HeadResourceId > 0 {
 		reportInfo.HeadResourceId = req.HeadResourceId
@@ -196,6 +227,8 @@ func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.A
 		reportInfo.EndResourceId = req.EndResourceId
 		updateCols = append(updateCols, "EndResourceId")
 	}
+	reportInfo.MiniShow = req.MiniShow
+	reportInfo.FreeReportRatio = req.FreeReportRatio
 
 	// 需要添加的报告授权数据
 	addReportAdminList := make([]*report.ReportGrant, 0)
@@ -260,6 +293,26 @@ func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.A
 		go handleReportPermission(int64(reportInfo.Id), minClassifyId)
 	}
 
+	// 更新es里的章节数据
+	if oldIsPublicPublish != reportInfo.IsPublicPublish {
+		if reportInfo.HasChapter == 1 {
+			// 晨周报
+			chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
+			if tmpErr != nil {
+				return
+			}
+			if len(chapterList) > 0 {
+				// 更新章节的es数据
+				for _, chapterInfo := range chapterList {
+					err = updateReportChapterEsByChapter(chapterInfo, reportInfo.IsPublicPublish)
+					if err != nil {
+						return
+					}
+				}
+			}
+		}
+	}
+
 	return
 }
 
@@ -313,6 +366,24 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		return
 	}
 
+	// 每篇章节对应的分页内容页
+	chapterFreeLayoutListMap := make(map[int][]*report.ReportFreeLayout)
+	{
+		allReportFreeLayoutList, tmpErr := report.GetAllReportFreeLayoutListByReportId(inheritReportId)
+		if tmpErr != nil {
+			errMsg = "获取自由布局内容页失败"
+			err = tmpErr
+			return
+		}
+		for _, reportFreeLayout := range allReportFreeLayoutList {
+			chapterFreeLayoutList, ok := chapterFreeLayoutListMap[reportFreeLayout.ReportChapterId]
+			if !ok {
+				chapterFreeLayoutList = make([]*report.ReportFreeLayout, 0)
+			}
+			chapterFreeLayoutListMap[reportFreeLayout.ReportChapterId] = append(chapterFreeLayoutList, reportFreeLayout)
+		}
+	}
+
 	// 待添加的章节
 	chapterTypeList := make([]*models.ReportChapterType, 0)
 	// 待添加的章节类型id列表
@@ -456,20 +527,22 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		// 继承的报告章节内容
 		for i := 0; i < len(inheritReportChapters); i++ {
 			customChapter := inheritReportChapters[i]
+			// 继承的报告章节ID
+			inheritReportChapterId := customChapter.ReportChapterId
 
 			// 授权用户列表
-			tmpGrantList, ok := grantListMap[customChapter.ReportChapterId]
+			tmpGrantList, ok := grantListMap[inheritReportChapterId]
 			if !ok {
 				tmpGrantList = make([]*report.ReportChapterGrant, 0)
 			}
-			oldChapterIdGrantListMap[customChapter.ReportChapterId] = tmpGrantList
+			oldChapterIdGrantListMap[inheritReportChapterId] = tmpGrantList
 
 			// 关联品种列表
-			chapterPermissionList, ok := chapterPermissionListMap[customChapter.ReportChapterId]
+			chapterPermissionList, ok := chapterPermissionListMap[inheritReportChapterId]
 			if !ok {
 				chapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
 			}
-			oldChapterPermissionListMap[customChapter.ReportChapterId] = chapterPermissionList
+			oldChapterPermissionListMap[inheritReportChapterId] = chapterPermissionList
 
 			// 判断该章节是否是系统章节,如果是的话,那就是需要额外创建的
 			if customChapter.TypeId > 0 {
@@ -491,9 +564,10 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 			customChapter.ContentModifyTime = time.Now()
 
 			customAddChapter := models.AddReportChapter{
-				ReportChapter:       customChapter,
-				GrantList:           tmpGrantList,
-				GrantPermissionList: chapterPermissionList,
+				ReportChapter:          customChapter,
+				GrantList:              tmpGrantList,
+				GrantPermissionList:    chapterPermissionList,
+				InheritReportChapterId: inheritReportChapterId,
 			}
 			customAddChapterList = append(customAddChapterList, customAddChapter)
 		}
@@ -502,6 +576,8 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 	// 最大排序
 	var maxSort int
 	for _, typeItem := range chapterTypeList {
+		// 继承的章节ID
+		inheritReportChapterId := 0
 		v, ok := inheritChapterMap[typeItem.ReportChapterTypeId]
 
 		// 章节授权用户
@@ -512,6 +588,8 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		chapterItem := new(models.ReportChapter)
 
 		if ok && v != nil {
+			inheritReportChapterId = v.ReportChapterId
+
 			// 如果存在继承的章节,那么就从继承的章节内容中获取
 			chapterItem.AddType = 2
 			chapterItem.Title = v.Title
@@ -556,6 +634,8 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 					return
 				}
 				if chapterNewest != nil {
+					inheritReportChapterId = chapterNewest.ReportChapterId
+
 					chapterItem.AddType = 2
 					chapterItem.Title = chapterNewest.Title
 					chapterItem.ReportType = chapterNewest.ReportType
@@ -651,9 +731,10 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		}
 
 		addChapter := models.AddReportChapter{
-			ReportChapter:       chapterItem,
-			GrantList:           tmpGrantList,
-			GrantPermissionList: tmpChapterPermissionList,
+			ReportChapter:          chapterItem,
+			GrantList:              tmpGrantList,
+			GrantPermissionList:    tmpChapterPermissionList,
+			InheritReportChapterId: inheritReportChapterId,
 		}
 
 		chapterList = append(chapterList, addChapter)
@@ -666,6 +747,27 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		chapterList = append(chapterList, addChapterItem)
 	}
 
+	for k, chapterItem := range chapterList {
+		reportFreeLayoutList, ok := chapterFreeLayoutListMap[chapterItem.InheritReportChapterId]
+		if ok {
+			tmpReportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+			for _, v := range reportFreeLayoutList {
+				tmpReportFreeLayoutList = append(tmpReportFreeLayoutList, &report.ReportFreeLayout{
+					Id:              0,
+					ReportId:        0,
+					ReportChapterId: 0,
+					Page:            v.Page,
+					IsChapter:       v.IsChapter,
+					Content:         v.Content,
+					ContentStruct:   v.ContentStruct,
+					CreateTime:      time.Now(),
+					ModifyTime:      time.Now(),
+				})
+			}
+			chapterList[k].ReportChapterFreeLayoutList = tmpReportFreeLayoutList
+		}
+	}
+
 	//hasGrantUserMap := make(map[int]bool)
 	//for _, grantList := range typeGrantListMap {
 	//	for _, grant := range grantList {
@@ -1154,12 +1256,29 @@ func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips
 		}
 		return
 	}
-
-	// 普通报告
-	if reportInfo.Content == "" {
-		errMsg = `报告内容为空,不可发布`
-		err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
-		return
+	if reportInfo.ReportLayout != 3 {
+		// 普通报告
+		if reportInfo.Content == "" {
+			errMsg = `报告内容为空,不可发布`
+			err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
+			return
+		}
+	} else {
+		pages, pageErr := report.GetFreeLayoutChapterPagesByReportId(reportInfo.Id)
+		if pageErr != nil {
+			errMsg = "获取自由布局报告失败,不可发布"
+			err = errors.New("获取自由布局报告失败,不可发布,Err:" + pageErr.Error())
+			return
+		}
+		var content string
+		for _, page := range pages {
+			content += page.Content
+		}
+		if content == "" {
+			errMsg = "自由布局报告内容为空,不可设置定时发布"
+			err = errors.New("自由布局报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportInfo.Id))
+			return
+		}
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
@@ -1207,7 +1326,7 @@ func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips
 
 	// 生成报告pdf和长图
 	{
-		reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ReportLayout)
+		reportPdfUrl := GetGeneralPdfUrl(reportInfo.Id, reportInfo.ReportCode, reportInfo.ReportLayout)
 		go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
 	}
 
@@ -1220,6 +1339,9 @@ func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips
 		go handleReportPermission(int64(reportInfo.Id), minClassifyId)
 	}
 
+	// 报告发布成功后,需要将相关信息入知识库
+	go cache.RagEtaReportOpToCache(reportInfo.Id, 0, `publish`)
+
 	return
 }
 
@@ -1334,10 +1456,13 @@ func PublishChapterReport(reportInfo *models.Report, reportUrl string, sysUser *
 
 	// 生成报告pdf和长图
 	{
-		reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ReportLayout)
+		reportPdfUrl := GetGeneralPdfUrl(reportInfo.Id, reportInfo.ReportCode, reportInfo.ReportLayout)
 		go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
 	}
 
+	// 报告发布成功后,需要将相关信息入知识库
+	go cache.RagEtaReportOpToCache(reportInfo.Id, 0, `publish`)
+
 	return
 }
 
@@ -1495,7 +1620,7 @@ func UpdateReportVideo(reportInfo *models.Report) {
 // @param reportCode string
 // @param reportLayout int8
 // @return pdfUrl string
-func GetGeneralPdfUrl(reportCode string, reportLayout int8) (pdfUrl string) {
+func GetGeneralPdfUrl(reportId int, reportCode string, reportLayout int8) (pdfUrl string) {
 	conf, e := models.GetBusinessConfByKey("ReportViewUrl")
 	if e != nil {
 		return
@@ -1510,5 +1635,408 @@ func GetGeneralPdfUrl(reportCode string, reportLayout int8) (pdfUrl string) {
 		pdfUrl = fmt.Sprintf("%s/reportshare_smart_pdf?code=%s", conf.ConfVal, reportCode)
 	}
 
+	if pdfUrl != "" {
+		token := utils.MD5(fmt.Sprint(pdfUrl, time.Now().UnixNano()/1e6))
+		e := generalReportAuthToken(token, ``, reportId)
+		if e == nil {
+			pdfUrl = fmt.Sprintf("%s&authToken=%s", pdfUrl, token)
+		}
+	}
+
+	return
+}
+
+// HandleReportContent
+// @Description: 处理报告内容(动态图表/表格添加授权token)
+// @author: Roc
+// @datetime 2025-01-07 10:03:15
+// @param body string
+// @return newBody string
+func HandleReportContent(body string, opType string, tokenMap map[string]string) (newBody string) {
+	if body == `` {
+		return
+	}
+	newBody = body
+
+	// 解析HTML
+	doc, err := html2.Parse(strings.NewReader(body))
+	if err != nil {
+		fmt.Println("Error parsing HTML:", err)
+		return
+	}
+
+	replaceIframeSrc(doc, opType, tokenMap)
+
+	// 输出修改后的HTML
+	var modifiedHtml strings.Builder
+	err = html2.Render(&modifiedHtml, doc)
+	if err != nil {
+		fmt.Println("Error rendering HTML:", err)
+		return
+	}
+
+	newBody = modifiedHtml.String()
+	fmt.Println(newBody)
+
+	return
+}
+
+// replaceIframeSrc 遍历HTML节点,替换iframe的src属性
+func replaceIframeSrc(n *html2.Node, opType string, tokenMap map[string]string) {
+	if n.Type == html2.ElementNode && n.Data == "iframe" {
+		for i, attr := range n.Attr {
+			if attr.Key == "src" {
+				newLink := attr.Val
+				// 处理链接
+				switch opType {
+				case `add`:
+					newLink = linkAddToken(attr.Val, tokenMap)
+				case `del`:
+					newLink = linkDelToken(attr.Val)
+				}
+				// 替换原来的链接
+				n.Attr[i].Val = newLink
+				break
+			}
+		}
+	}
+	// 递归处理子节点
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		replaceIframeSrc(c, opType, tokenMap)
+	}
+}
+
+// linkAddToken 链接添加token
+func linkAddToken(link string, tokenMap map[string]string) string {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("处理链接失败,ERR:" + err.Error())
+		}
+	}()
+	parsedURL, err := url.Parse(link)
+	if err != nil {
+		return link
+	}
+
+	// 获取查询参数
+	queryParams := parsedURL.Query()
+
+	// 先移除authToken参数,避免莫名其妙的这个值入库了
+	queryParams.Del("authToken")
+
+	// 获取code参数
+	code := queryParams.Get("code")
+	if code == "" {
+		return link
+	}
+
+	showType := `chart`
+	if strings.Contains(parsedURL.Path, "sheetshow") {
+		showType = `excel`
+	}
+
+	// 避免报告里面一个图表/表格重复生成token
+	var token string
+	key := fmt.Sprint(showType, `:`, code)
+	if tokenMap != nil {
+		token = tokenMap[key]
+	}
+
+	// 如果之前没有token,那么就重新生成token
+	if token == `` {
+		token, err = GeneralChartToken(showType, code)
+		if err != nil {
+			return link
+		}
+	}
+
+	if tokenMap != nil {
+		tokenMap[key] = token
+	}
+
+	// 在链接后面添加一个token值
+	queryParams.Add("authToken", token)
+
+	// 更新URL的查询参数
+	parsedURL.RawQuery = queryParams.Encode()
+
+	return parsedURL.String()
+}
+
+// linkDelToken 链接添加token
+func linkDelToken(link string) string {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("处理链接失败,ERR:" + err.Error())
+		}
+	}()
+	parsedURL, err := url.Parse(link)
+	if err != nil {
+		return link
+	}
+
+	// 获取查询参数
+	queryParams := parsedURL.Query()
+
+	// 移除authToken参数
+	queryParams.Del("authToken")
+
+	// 更新URL的查询参数
+	parsedURL.RawQuery = queryParams.Encode()
+
+	return parsedURL.String()
+}
+
+// GeneralChartToken
+// @Description: 生产图表/表格授权token
+// @author: Roc
+// @datetime 2025-01-07 10:41:36
+// @param showType string
+// @param uniqueCode string
+// @return token string
+// @return err error
+func GeneralChartToken(showType, uniqueCode string) (token string, err error) {
+	// 缓存key
+	token = utils.MD5(fmt.Sprint(showType+`:`, uniqueCode, time.Now().UnixNano()/1e6))
+	key := fmt.Sprint(utils.CACHE_CHART_AUTH, token)
+	err = utils.Rc.Put(key, uniqueCode, utils.BusinessConfReportChartExpiredTime)
+
+	return
+}
+
+// GeneralReportToken
+// @Description: 生成报告授权token
+// @author: Roc
+// @datetime 2025-01-07 10:41:36
+// @param uniqueCode string
+// @return token string
+// @return err error
+func GeneralReportToken(linkToken string, reportId int) (token string, err error) {
+	// 图表授权token
+	token = utils.MD5(fmt.Sprint(linkToken, time.Now().UnixNano()/1e6))
+
+	// 缓存key
+	reportKey := getReportShareTokenKey(linkToken)
+	err = utils.Rc.Put(reportKey, token, utils.BusinessConfReportChartExpiredTime)
+	if err != nil {
+		return
+	}
+
+	// 生成报告的图表授权token
+	err = generalReportAuthToken(token, ``, reportId)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// generalReportAuthToken
+// @Description: 生成报告的图表授权token
+// @author: Roc
+// @datetime 2025-03-17 17:47:07
+// @param token string
+// @param reportId int
+// @return err error
+func generalReportAuthToken(token, source string, reportId int) (err error) {
+	// 缓存key
+	reportTokenKey := getReportTokenKey(token, source)
+	err = utils.Rc.Put(reportTokenKey, reportId, utils.BusinessConfReportChartExpiredTime)
+
+	return
+}
+
+// getReportShareTokenKey
+// @Description:
+// @author: Roc
+// @datetime 2025-03-17 14:00:14
+// @param linkToken string
+// @return string
+func getReportShareTokenKey(linkToken string) string {
+	return fmt.Sprint(utils.CACHE_REPORT_SHARE_AUTH, utils.MD5(linkToken))
+}
+
+// GetReportAuthToken
+// @Description: 获取报告token
+// @author: Roc
+// @datetime 2025-03-17 16:48:38
+// @param linkToken string
+// @return string
+func GetReportAuthToken(linkToken string) string {
+	key := getReportShareTokenKey(linkToken)
+	return utils.Rc.GetStr(key)
+
+}
+
+// getReportTokenKey
+// @Description:
+// @author: Roc
+// @datetime 2025-03-17 14:00:14
+// @param linkToken string
+// @return string
+func getReportTokenKey(token, source string) string {
+	return fmt.Sprint(utils.CACHE_REPORT_AUTH, source, token)
+}
+
+// HandleReportContentStruct
+// @Description: 处理内容组件的链接
+// @author: Roc
+// @datetime 2025-01-07 13:38:39
+// @param body string
+// @param opType string
+// @return newBody string
+func HandleReportContentStruct(body string, opType string, tokenMap map[string]string) (newBody string) {
+	if body == `` {
+		return
+	}
+	newBody = body
+
+	// 解析JSON数据到map[string]interface{}
+	var jsonData []map[string]interface{}
+	if err := json.Unmarshal([]byte(body), &jsonData); err != nil {
+		fmt.Println("Error parsing JSON:", err)
+		return
+	}
+
+	// 处理每个组件
+	for i := range jsonData {
+		if err := processMap(jsonData[i], opType, tokenMap); err != nil {
+			fmt.Println("Error processing component:", err)
+			return
+		}
+	}
+
+	// 将处理后的数据转换回JSON字符串
+	modifiedJSON, err := json.MarshalIndent(jsonData, "", "  ")
+	if err != nil {
+		fmt.Println("Error marshaling JSON:", err)
+		return
+	}
+	newBody = string(modifiedJSON)
+
+	return
+}
+
+// processMap 递归处理map中的content字段
+func processMap(data map[string]interface{}, opType string, tokenMap map[string]string) error {
+	for key, value := range data {
+		switch v := value.(type) {
+		case string:
+			if key == "content" {
+				newContent := v
+				// 处理链接
+				switch opType {
+				case `add`:
+					newContent = linkAddToken(v, tokenMap)
+				case `del`:
+					newContent = linkDelToken(v)
+				}
+				data[key] = newContent
+			}
+		case map[string]interface{}:
+			if err := processMap(v, opType, tokenMap); err != nil {
+				return err
+			}
+		case []interface{}:
+			for i := range v {
+				if m, ok := v[i].(map[string]interface{}); ok {
+					if err := processMap(m, opType, tokenMap); err != nil {
+						return err
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// GetReportShareUrlToken 获取报告分享链接token
+func GetReportShareUrlToken(req models.ReportShartUrlReq, adminId int) (linkToken string, err error) {
+	defer func() {
+		if err == nil && linkToken != `` {
+			GeneralReportToken(linkToken, req.ReportId)
+		}
+	}()
+	cacheLinkKey := utils.CACHE_REPORT_SHARE_SHORT_Url + strconv.Itoa(req.ReportId) + "userId:" + strconv.Itoa(adminId)
+	linkToken, _ = utils.Rc.RedisString(cacheLinkKey)
+	if linkToken != "" && utils.Rc.IsExist(fmt.Sprint(utils.CACHE_REPORT_SHARE_ORIGIN_Url, utils.MD5(linkToken))) {
+		return
+	}
+	var tokenKey string
+
+	var ok bool
+	// 冲突检测
+	for i := 0; i < 3; i++ {
+		linkToken = req.Url
+		if i > 0 {
+			linkToken += "_" + utils.GetRandDigit(3)
+		}
+		hashUrl := utils.MurmurHash64([]byte(linkToken))
+		linkToken = utils.ConvertNumToBase62(hashUrl)
+		// 拼上报告标题
+		//linkToken = fmt.Sprintf("%s %s", linkToken, req.Title)
+
+		tokenKey = fmt.Sprint(utils.CACHE_REPORT_SHARE_ORIGIN_Url, utils.MD5(linkToken))
+		ok = utils.Rc.IsExist(tokenKey)
+		if !ok {
+			break
+		}
+	}
+	if !ok {
+		after := time.Now().AddDate(0, 0, 7)
+		err = utils.Rc.Put(cacheLinkKey, linkToken, time.Until(after))
+		if err != nil {
+			return
+		}
+		err = utils.Rc.Put(tokenKey, req.Url, time.Until(after))
+		if err != nil {
+			return
+		}
+	} else {
+		linkToken = ""
+		err = errors.New("生成链接失败")
+	}
+	return
+}
+
+func TransfromToOriginUrl(linkToken string) (originLink string, msg string, err error) {
+	cacheLinkKey := fmt.Sprint(utils.CACHE_REPORT_SHARE_ORIGIN_Url, utils.MD5(linkToken))
+	originLink, err = utils.Rc.RedisString(cacheLinkKey)
+	if err != nil {
+		if err == redis.Nil {
+			msg = "链接已失效, 请重新获取"
+			return
+		}
+		msg = "获取链接失败"
+		return
+	}
+	if originLink == "" {
+		msg = "链接已失效, 请重新获取"
+		return
+	}
+
+	reportToken := GetReportAuthToken(linkToken)
+	if reportToken != "" {
+		originLink += `&authToken=` + reportToken
+	}
+
 	return
 }
+
+func FilterShareUrl() web.FilterFunc {
+	return func(c *context.Context) {
+		path := c.Input.Context.Request.URL.Path
+		tokenArr := strings.Split(path, "/")
+		token := tokenArr[len(tokenArr)-1]
+
+		newPath := "/v1/report/share/link"
+		q := c.Input.Context.Request.URL.Query()
+		q.Add("Token", token)
+		c.Input.Context.Request.URL.Path = newPath
+		c.Input.Context.Request.URL.RawQuery = q.Encode()
+
+		utils.ApiLog.Info(fmt.Sprintf("原始请求为:%s, 已修改请求路径为:%s?%s", path, newPath, q.Encode()))
+	}
+}

+ 228 - 36
services/smart_report.go

@@ -8,10 +8,12 @@ import (
 	"eta/eta_mobile/utils"
 	"fmt"
 	"html"
+	"math"
 	"os"
 	"os/exec"
 	"path"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -136,7 +138,7 @@ func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
 	return
 }
 
-func ReportToPdf(reportUrl, filePath string) (err error) {
+func ReportToPdf(width, height int, reportUrl, filePath string, top, bottom, left, right int) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch
@@ -151,26 +153,27 @@ async def main():
     })
     page = await browser.newPage()
     await page.setViewport({
-        'width': 1920,
-        'height': 1080,
+        'width': %d,
+        'height': %d
     })
     await page.goto('%s', {
         'waitUntil': 'networkidle0',
-        'timeout': 1000000  # 设置超时时间为 100 秒
+        'timeout': 3000000  # 设置超时时间为 100 秒
     })
 
     # 在生成PDF之前等待2秒
-    await asyncio.sleep(5)
+    await asyncio.sleep(15)
 
     await page.pdf({
+		'width': %d,
+        'height': %d,
         'path': "%s",
         'printBackground': True,
-        'format': "A2",
         'margin': {
-            'top': '10mm',
-            'bottom': '10mm',
-            'left': '10mm',
-            'right': '10mm'
+            'top': '%dpx',
+            'bottom': '%dpx',
+            'left': '%dpx',
+            'right': '%dpx'
         }
     })
     await browser.close()
@@ -186,7 +189,7 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, reportUrl, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, height, reportUrl, width+left+right, height+top+bottom, filePath, top, bottom, left, right)
 	utils.FileLog.Info("pdf pyCode: \n" + pyCode)
 	cmd := exec.Command("python3", "-c", pyCode)
 	output, e := cmd.CombinedOutput()
@@ -203,7 +206,7 @@ finally:
 	return
 }
 
-func ReportToJpeg(width int, reportUrl, filePath string) (err error) {
+func ReportToJpeg(width, height int, reportUrl, filePath string) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch, errors
@@ -223,23 +226,23 @@ async def main():
         # 设置视口大小
         await page.setViewport({
             'width': %d,
-            'height': 1080
+            'height': %d
         })
         
         # 导航到页面
         await page.goto('%s', {
             'waitUntil': 'networkidle0',
-            'timeout': 1000000  # 设置超时时间为 100 秒
+            'timeout': 3000000  # 设置超时时间为 100 秒
         })
         # Customizing footer for page numbers starting from page 2
 
         # 在这里添加两秒的等待
-        await asyncio.sleep(2)
+        await asyncio.sleep(5)
 
         await page.screenshot({
             'path': "%s",
             'fullPage': True,
-			'quality':100
+			'quality':80
         })
         
     except errors.BrowserError as e:
@@ -264,10 +267,9 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, height, reportUrl, filePath)
 	utils.FileLog.Info("jpeg pyCode: \n" + pyCode)
 	cmd := exec.Command("python3", "-c", pyCode)
-
 	output, e := cmd.CombinedOutput()
 	if e != nil {
 		err = e
@@ -302,14 +304,29 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 	if reportUrl == `` {
 		return
 	}
+	var report *models.ReportDetail
+
+	// 默认宽高,自由布局需要根据比例重新计算
+	var freeRatio string
+	pcWidth := 1200
+	pcHeight := 1697
+	mobileWidth := 600
+	mobileHeight := 1697
 
 	// 先清空字段
 	if reportType == 1 {
+		report, err = models.GetReportById(reportId)
+		if err != nil {
+			return
+		}
 		err = models.UpdatePdfUrlReportById(reportId)
 		if err != nil {
 			utils.FileLog.Info("清空pdf长图字段失败, Err: \n" + err.Error())
 			return
 		}
+		if report.ReportLayout == 3 {
+			freeRatio = report.FreeReportRatio
+		}
 	} else if reportType == 2 {
 		err = models.UpdatePdfUrlEnglishReportById(reportId)
 		if err != nil {
@@ -324,13 +341,41 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 		}
 	}
 
-	reportCode := utils.MD5(strconv.Itoa(reportId))
+	// 重新计算比例
+	if freeRatio != "" {
+		h, e := calculateReportPdfHeight(pcWidth, freeRatio)
+		if e != nil {
+			err = fmt.Errorf("重新计算PDF生成高度失败, %v", e)
+			return
+		}
+		pcHeight = h
 
-	pdfPath := `./static/` + reportCode + ".pdf"
-	jpegPath := `./static/` + reportCode + ".jpeg"
+		hm, e := calculateReportPdfHeight(mobileWidth, freeRatio)
+		if e != nil {
+			err = fmt.Errorf("重新计算移动端PDF生成高度失败, %v", e)
+			return
+		}
+		mobileHeight = hm
+	}
 
+	reportCode := utils.MD5(strconv.Itoa(reportId))
+
+	// pc端
 	go func() {
-		err := ReportToPdf(reportUrl, pdfPath)
+		pdfPath := `./static/` + reportCode + "_1200.pdf"
+		jpegPath := `./static/` + reportCode + "_1200.jpg"
+
+		//width := 1200
+		top, bottom, left, right := 20, 20, 20, 20
+		if reportType == 1 {
+			if report != nil && report.ReportLayout == 3 {
+				top, bottom, left, right = 0, 0, 0, 0
+			}
+		}
+		//if reportType == 3 {
+		//	width = 800
+		//}
+		err = ReportToPdf(pcWidth, pcHeight, reportUrl, pdfPath, top, bottom, left, right)
 		if err != nil {
 			utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
 			go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
@@ -388,31 +433,26 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 			}
 		}
 
-	}()
+		time.Sleep(1 * time.Minute)
 
-	go func() {
-		width := 1200
-		if reportType == 3 {
-			width = 800
-		}
-		err := ReportToJpeg(width, reportUrl, jpegPath)
+		err = ReportToJpeg(pcWidth, pcHeight, reportUrl, jpegPath)
 		if err != nil {
 			utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
 		}
-		file, err := os.Open(jpegPath)
+		file, err = os.Open(jpegPath)
 		if err != nil {
 			utils.FileLog.Info("open file failed: , error: \n" + err.Error())
 			return
 		}
 
-		ext := path.Ext(file.Name())
+		ext = path.Ext(file.Name())
 
-		randStr := utils.GetRandStringNoSpecialChar(28)
-		fileName := randStr + ext
+		randStr = utils.GetRandStringNoSpecialChar(28)
+		fileName = randStr + ext
 		defer file.Close() //关闭上传文件
 
-		resourceUrl := ``
-		ossClient := NewOssClient()
+		resourceUrl = ``
+		ossClient = NewOssClient()
 		if ossClient == nil {
 			utils.FileLog.Info("初始化OSS服务失败")
 			return
@@ -448,6 +488,158 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 				return
 			}
 		}
-
 	}()
+	var mobilePdf = true
+	if reportType == 1 && report != nil {
+		if report.ReportLayout == 3 {
+			mobilePdf = false
+		}
+	}
+	if mobilePdf {
+		// 移动端
+		go func() {
+			pdfPathMobile := `./static/` + reportCode + "_600.pdf"
+			jpegPathMobile := `./static/` + reportCode + "_600.jpg"
+			top, bottom, left, right := 20, 20, 20, 20
+			//width := 600
+			err = ReportToPdf(mobileWidth, mobileHeight, reportUrl, pdfPathMobile, top, bottom, left, right)
+			if err != nil {
+				utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
+				go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
+			}
+
+			file, err := os.Open(pdfPathMobile)
+			if err != nil {
+				utils.FileLog.Info("Open failed: , error: \n" + err.Error())
+				go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
+				return
+			}
+
+			ext := path.Ext(file.Name())
+
+			randStr := utils.GetRandStringNoSpecialChar(28)
+			fileName := randStr + ext
+			defer file.Close() //关闭上传文件
+
+			resourceUrl := ``
+			ossClient := NewOssClient()
+			if ossClient == nil {
+				utils.FileLog.Info("初始化OSS服务失败")
+				return
+			}
+			resourceUrl, err = ossClient.UploadFile(fileName, pdfPathMobile, "")
+			if err != nil {
+				utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+				go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
+				return
+			}
+			defer func() {
+				_ = os.Remove(pdfPathMobile)
+			}()
+
+			if reportType == 3 {
+				// 更新pdf url
+				ob := new(smart_report.SmartReport)
+				ob.SmartReportId = reportId
+				ob.DetailPdfUrlMobile = resourceUrl
+				if err = ob.Update([]string{"DetailPdfUrlMobile"}); err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 2 {
+				err = models.ModifyEnglishReportPdfUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 1 {
+				err = models.ModifyReportPdfUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			}
+
+			time.Sleep(1 * time.Minute)
+
+			err = ReportToJpeg(mobileWidth, mobileHeight, reportUrl, jpegPathMobile)
+			if err != nil {
+				utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
+			}
+			file, err = os.Open(jpegPathMobile)
+			if err != nil {
+				utils.FileLog.Info("open file failed: , error: \n" + err.Error())
+				return
+			}
+
+			ext = path.Ext(file.Name())
+
+			randStr = utils.GetRandStringNoSpecialChar(28)
+			fileName = randStr + ext
+			defer file.Close() //关闭上传文件
+
+			resourceUrl = ``
+			ossClient = NewOssClient()
+			if ossClient == nil {
+				utils.FileLog.Info("初始化OSS服务失败")
+				return
+			}
+			resourceUrl, err = ossClient.UploadFile(fileName, jpegPathMobile, "")
+			if err != nil {
+				utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+				return
+			}
+			defer func() {
+				_ = os.Remove(jpegPathMobile)
+			}()
+
+			if reportType == 3 {
+				// 更新jpeg url
+				ob := new(smart_report.SmartReport)
+				ob.SmartReportId = reportId
+				ob.DetailImgUrlMobile = resourceUrl
+				if err = ob.Update([]string{"DetailImgUrlMobile"}); err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 2 {
+				err = models.ModifyEnglishReportImgUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 1 {
+				err = models.ModifyReportImgUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			}
+		}()
+	}
+}
+
+// calculateReportPdfHeight 自由布局-根据宽度和布局比例计算高度
+func calculateReportPdfHeight(width int, freeRatio string) (height int, err error) {
+	ratioArr := strings.Split(freeRatio, "/")
+	if len(ratioArr) != 2 {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	m, e := strconv.ParseFloat(ratioArr[0], 64)
+	if e != nil {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	n, e := strconv.ParseFloat(ratioArr[1], 64)
+	if e != nil {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	if m <= 0 {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	height = int(math.Round(float64(width) * n / m))
+	return
 }

+ 2 - 0
services/sms.go

@@ -209,6 +209,8 @@ func NewSmsClient() (cli SmsClient, err error) {
 	}
 	if confMap[models.BusinessConfSmsClient] == models.BusinessConfClientFlagNanHua {
 		return new(NanHuaSms), nil
+	} else if confMap[models.BusinessConfSmsClient] == models.BusinessConfClientFlagDongwu {
+		return new(DongWuSms), nil
 	}
 	return new(HzSms), nil
 }

+ 5 - 0
utils/business_conf.go

@@ -0,0 +1,5 @@
+package utils
+
+import "time"
+
+var BusinessConfReportChartExpiredTime time.Duration //图表有效期鉴权时间,单位:分钟

+ 89 - 4
utils/common.go

@@ -11,10 +11,6 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/PuerkitoBio/goquery"
-	"github.com/microcosm-cc/bluemonday"
-	"github.com/shopspring/decimal"
-	xhtml "golang.org/x/net/html"
 	"html"
 	"image"
 	"image/png"
@@ -31,6 +27,12 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/PuerkitoBio/goquery"
+	"github.com/microcosm-cc/bluemonday"
+	"github.com/shopspring/decimal"
+	"github.com/spaolacci/murmur3"
+	xhtml "golang.org/x/net/html"
 )
 
 // 随机数种子
@@ -2562,3 +2564,86 @@ func GetDuration(filePath string) (duration string, err error) {
 
 	return duration, nil
 }
+
+// MurmurHash64 计算字符串的64位哈希值
+func MurmurHash64(val []byte) uint64 {
+	hash64 := murmur3.New64()
+	hash64.Write(val)
+	return hash64.Sum64()
+}
+
+const base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+type TUint interface {
+	uint64 | uint32 | uint16 | uint8 | uint
+}
+
+// ConvertNumToBase62 转换数字为base62编码
+func ConvertNumToBase62[T TUint](num T) string {
+	if num == 0 {
+		return string(base62Chars[0])
+	}
+
+	var result []byte
+	for num > 0 {
+		remainder := num % 62
+		result = append(result, base62Chars[remainder])
+		num /= 62
+	}
+
+	// 反转结果
+	for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
+		result[i], result[j] = result[j], result[i]
+	}
+
+	return string(result)
+}
+
+// FindMinMax 取出数组中的最小值和最大值
+func FindMinMax(numbers []float64) (min float64, max float64) {
+	if len(numbers) == 0 {
+		return 0, 0 // 如果切片为空,返回0, 0
+	}
+
+	min, max = numbers[0], numbers[0] // 初始化 min 和 max 为切片的第一个元素
+
+	for _, num := range numbers {
+		if num < min {
+			min = num
+		}
+		if num > max {
+			max = num
+		}
+	}
+
+	return min, max
+}
+
+// GetWindWsdIndexCodeSuffix 获取wind日期序列指标编码后缀
+func GetWindWsdIndexCodeSuffix(period, days, priceAdj string) (suffix string) {
+	if period != "" && period != WindWsdPeriodDefault {
+		suffix += period
+	}
+	if days != "" && days != WindWsdDaysDefault {
+		suffixDays := map[string]string{
+			WindWsdDaysWeekdays: WindWsdSuffixWeekdays,
+			WindWsdDaysAlldays:  WindWsdSuffixAlldays,
+		}
+		suffix += suffixDays[days]
+	}
+	if priceAdj != "" {
+		suffix += priceAdj
+	}
+	return
+}
+
+// GetThsDsIndexCodeSuffix 获取同花顺日期序列指标编码后缀
+func GetThsDsIndexCodeSuffix(period, days string) (suffix string) {
+	if period != "" && period != WindWsdPeriodDefault {
+		suffix += period
+	}
+	if days == ThsDsDaysAlldays {
+		suffix += ThsDsDaysSuffixAlldays
+	}
+	return
+}

+ 40 - 2
utils/constants.go

@@ -119,7 +119,7 @@ const (
 	DATA_SOURCE_PREDICT_CALCULATE                               //预测指标运算->31
 	DATA_SOURCE_PREDICT_CALCULATE_TBZ                           //预测同比值->32
 	DATA_SOURCE_PREDICT_CALCULATE_TCZ                           //预测同差值->33
-	DATA_SOURCE_MYSTEEL_CHEMICAL                                //钢联化工->34
+	DATA_SOURCE_MYSTEEL_CHEMICAL                                //上海钢联->34
 	DATA_SOURCE_CALCULATE_CJJX                                  //超季节性->35
 	DATA_SOURCE_EIA_STEO                                        //eia steo报告->36
 	DATA_SOURCE_CALCULATE_NHCC                                  //计算指标(拟合残差)->37
@@ -208,7 +208,7 @@ const (
 	CACHE_LOGIN_ERR_PASS              = "mobile_eta_admin:login:errPass:"   //管理后台登录-输入错误密码次数
 	CACHE_FIND_PASS_VERIFY            = "mobile_eta_admin:findPass:verify:" //找回密码校验成功标记
 	CACHE_KEY_COMPANY_MATCH_PRE       = "admin:company:match:"              //客户名单匹配
-	CACHE_KEY_MYSTEEL_REFRESH         = "mysteel_chemical:refresh"          //钢联化工刷新
+	CACHE_KEY_MYSTEEL_REFRESH         = "mysteel_chemical:refresh"          //上海钢联刷新
 	CACHE_KEY_DAYNEW_REFRESH          = "admin:day_new:refresh"             //每日资讯拉取企业微信聊天记录
 	CACHE_KEY_DAYNEW_TRANSLATE        = "admin:day_new:translate"           //每日资讯中翻英
 	CACHE_CREATE_REPORT_IMGPDF_QUEUE  = "eta_report:report_img_pdf_queue"   // 生成报告长图PDF队列
@@ -218,6 +218,12 @@ const (
 	CACHE_SMART_REPORT_EDITING        = "eta:smart_report:editing:"         // 智能研报用户编辑中
 	CACHE_SMART_REPORT_SEND_MSG       = "eta:smart_report:sending:"         // 智能研报用户报告推送
 	CACHE_KEY_REPLACE_EDB             = "eta:replace_edb"                   //系统用户操作日志队列
+	CACHE_REPORT_SHARE_SHORT_Url      = "eta:report_share_url:report_id:"   //报告短链映射key
+	CACHE_REPORT_SHARE_ORIGIN_Url     = "eta:report_share_url:token:"       //短链与原始报告链接的映射key
+	CACHE_CHART_AUTH                  = "eta:chart:auth:"                   //图表数据授权
+	CACHE_REPORT_SHARE_AUTH           = "eta:report:auth:share:"            //报告短链与报告图表授权映射key
+	CACHE_REPORT_AUTH                 = "eta:report:auth:"                  //报告图表数据授权
+	CACHE_ETA_REPORT_KNOWLEDGE        = "eta:report:knowledge:op:"          //eta报告入知识库处理
 )
 
 // 模板消息推送类型
@@ -325,6 +331,9 @@ const (
 	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
 	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
+	CHART_SOURCE_TRADE_ANALYSIS_PROCESS          = 13 // 持仓分析-建仓过程图表
+	CHART_SOURCE_AI_PREDICT_MODEL_DAILY          = 14 // AI预测模型图表-日度预测
+	CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY        = 15 // AI预测模型图表-月度预测
 )
 
 // 批量配置图表的位置来源
@@ -475,3 +484,32 @@ const (
 	ZhLangVersion = "zh" // 中文语言版本
 	EnLangVersion = "en" // 英文语言版本
 )
+
+// 图表分类设置精选资源分类
+const (
+	ChartClassifyIsSelected            = 1 // 图表分类设置精选资源分类
+	ChartClassifyResourceStatusUp      = 1 // 图表分类上架状态
+	ChartClassifyResourceStatusDown    = 2 // 图表分类下架状态
+	ChartClassifyResourceStatusDefault = 0 // 图表分类默认状态
+)
+
+// 日期序列
+const (
+	WindWsdDaysDefault      = "Trading"   // 日期选项:交易日参数值
+	WindWsdDaysWeekdays     = "Weekdays"  // 日期选项:工作日参数值
+	WindWsdDaysAlldays      = "Alldays"   // 日期选项:日历日参数值
+	WindWsdSuffixWeekdays   = "W"         // 指标编码后缀:工作日
+	WindWsdSuffixAlldays    = "A"         // 指标编码后缀:日历日
+	WindWsdPeriodDefault    = "D"         // 周期:天
+	WindWsdPeriodWeek       = "W"         // 周期:周
+	WindWsdPeriodMonth      = "M"         // 周期:月
+	WindWsdPeriodQuarter    = "Q"         // 周期:季
+	WindWsdPeriodYear       = "Y"         // 周期:年
+	WindWsdRestorationFront = "F"         // 复权方式:前复权
+	WindWsdRestorationBack  = "B"         // 复权方式:后复权
+	WindWsdRestorationFixed = "T"         // 复权方式:定点复权
+	ThsDsDaysDefault        = "Tradedays" // 日期选项:交易日参数值
+	ThsDsDaysAlldays        = "Alldays"   // 日期选项:日历日参数值
+	ThsDsDaysSuffixAlldays  = "A"         // 指标编码后缀:日历日
+	ThsDsdPeriodHalfYear    = "S"         // 周期:半年(其他频度与wind一致)
+)

+ 2 - 0
utils/redis.go

@@ -7,6 +7,8 @@ import (
 
 type RedisClient interface {
 	Get(key string) interface{}
+	GetStr(key string) string
+	GetUInt64(key string) (uint64, error)
 	RedisBytes(key string) (data []byte, err error)
 	RedisString(key string) (data string, err error)
 	RedisInt(key string) (data int, err error)

+ 19 - 0
utils/redis/cluster_redis.go

@@ -86,6 +86,25 @@ func (rc *ClusterRedisClient) Get(key string) interface{} {
 	return data
 }
 
+// GetStr
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return string
+func (rc *ClusterRedisClient) GetStr(key string) string {
+	return rc.redisClient.Get(context.TODO(), key).Val()
+}
+
+// GetUInt64
+// @Description: 根据key获取uint64数据
+// @receiver rc
+// @param key
+// @return int
+// @return error
+func (rc *ClusterRedisClient) GetUInt64(key string) (uint64, error) {
+	return rc.redisClient.Get(context.TODO(), key).Uint64()
+}
+
 // RedisBytes
 // @Description: 根据key获取字节编码数据
 // @receiver rc

+ 19 - 0
utils/redis/standalone_redis.go

@@ -78,6 +78,25 @@ func (rc *StandaloneRedisClient) Get(key string) interface{} {
 	return data
 }
 
+// GetStr
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return string
+func (rc *StandaloneRedisClient) GetStr(key string) string {
+	return rc.redisClient.Get(context.TODO(), key).Val()
+}
+
+// GetUInt64
+// @Description: 根据key获取uint64数据
+// @receiver rc
+// @param key
+// @return int
+// @return error
+func (rc *StandaloneRedisClient) GetUInt64(key string) (uint64, error) {
+	return rc.redisClient.Get(context.TODO(), key).Uint64()
+}
+
 // RedisBytes
 // @Description: 根据key获取字节编码数据
 // @receiver rc