Ver código fonte

Merge remote-tracking branch 'origin/master' into pool/94

Roc 3 meses atrás
pai
commit
27759b46fa
100 arquivos alterados com 21303 adições e 1826 exclusões
  1. 1 1
      .idea/vcs.xml
  2. 100 47
      controllers/classify.go
  3. 72 0
      controllers/data_manage/chart_common.go
  4. 83 3
      controllers/data_manage/chart_info.go
  5. 6 2
      controllers/data_manage/correlation/correlation_chart_info.go
  6. 6 2
      controllers/data_manage/cross_variety/chart_info.go
  7. 45 0
      controllers/data_manage/edb_info.go
  8. 338 0
      controllers/data_manage/excel/balance_table.go
  9. 366 12
      controllers/data_manage/excel/excel_info.go
  10. 78 53
      controllers/data_manage/future_good/future_good_chart_info.go
  11. 6 2
      controllers/data_manage/line_equation/line_chart_info.go
  12. 6 2
      controllers/data_manage/line_feature/chart_info.go
  13. 18 106
      controllers/data_manage/predict_edb_info.go
  14. 666 0
      controllers/data_manage/range_analysis/chart_classify.go
  15. 1452 0
      controllers/data_manage/range_analysis/chart_info.go
  16. 479 0
      controllers/document_manage/document_manage_controller.go
  17. 168 152
      controllers/english_report/report.go
  18. 233 0
      controllers/eta_forum/eta_forum.go
  19. 1741 0
      controllers/material/material.go
  20. 438 493
      controllers/report.go
  21. 1 1
      controllers/report_approve/report_approve.go
  22. 61 38
      controllers/report_approve/report_approve_flow.go
  23. 1721 0
      controllers/report_chapter.go
  24. 50 63
      controllers/report_chapter_type.go
  25. 2001 0
      controllers/report_v2.go
  26. 106 1
      controllers/resource.go
  27. 13 36
      controllers/smart_report/smart_report.go
  28. 374 0
      controllers/sys_department.go
  29. 34 1
      controllers/user_login.go
  30. 104 38
      controllers/voice.go
  31. 10 5
      go.mod
  32. 20 9
      go.sum
  33. 73 34
      models/business_conf.go
  34. 79 0
      models/chart_permission.go
  35. 295 92
      models/classify.go
  36. 46 0
      models/company/company_config.go
  37. 25 0
      models/data_manage/chart_classify.go
  38. 36 7
      models/data_manage/chart_info.go
  39. 438 0
      models/data_manage/chart_info_range_analysis.go
  40. 33 0
      models/data_manage/data_manage_permission/excel.go
  41. 2 0
      models/data_manage/edb_data_base.go
  42. 1 0
      models/data_manage/edb_info.go
  43. 107 15
      models/data_manage/excel/excel_info.go
  44. 52 29
      models/data_manage/excel/response/excel_info.go
  45. 62 0
      models/data_manage/factor_edb_series.go
  46. 210 0
      models/data_manage/factor_edb_series_calculate_data_qjjs.go
  47. 8 0
      models/data_manage/factor_edb_series_chart_mapping.go
  48. 8 0
      models/data_manage/multiple_graph_config_edb_mapping.go
  49. 12 0
      models/data_manage/my_chart.go
  50. 27 6
      models/db.go
  51. 162 0
      models/document_manage_model/outside_report.go
  52. 46 0
      models/document_manage_model/outside_report_attachment.go
  53. 77 3
      models/english_report.go
  54. 245 0
      models/material/material.go
  55. 215 0
      models/material/material_classify.go
  56. 26 0
      models/move_interface.go
  57. 95 7
      models/permission.go
  58. 1 0
      models/ppt_english/ppt_english.go
  59. 39 1
      models/ppt_english/ppt_english_group_mapping.go
  60. 33 2
      models/ppt_v2_group_mapping.go
  61. 842 113
      models/report.go
  62. 110 0
      models/report/report_chapter_grant.go
  63. 152 0
      models/report/report_chapter_permission_mapping.go
  64. 126 0
      models/report/report_grant.go
  65. 13 4
      models/report_approve/report_approve.go
  66. 12 1
      models/report_approve/report_approve_flow.go
  67. 39 0
      models/report_approve/report_approve_record.go
  68. 351 43
      models/report_chapter.go
  69. 2 2
      models/report_chapter_ticker.go
  70. 71 24
      models/report_chapter_type.go
  71. 60 22
      models/report_chapter_type_permission.go
  72. 453 0
      models/report_v2.go
  73. 3 2
      models/smart_report/smart_report.go
  74. 6 0
      models/system/sys_group.go
  75. 60 0
      models/user_collect_classify.go
  76. 5 4
      models/wechat_send_msg.go
  77. 665 26
      routers/commentsRouter.go
  78. 33 0
      routers/router.go
  79. 10 8
      services/chart_permission_sync.go
  80. 849 0
      services/classify.go
  81. 592 0
      services/data/area_graph/processor_business_logic.go
  82. 27 0
      services/data/area_graph/processor_factory.go
  83. 26 0
      services/data/chart_classify.go
  84. 52 62
      services/data/chart_info.go
  85. 1 1
      services/data/chart_info_elastic.go
  86. 19 45
      services/data/chart_info_excel_balance.go
  87. 1 1
      services/data/chart_theme.go
  88. 122 0
      services/data/edb_info_relation.go
  89. 66 0
      services/data/excel/balance_table.go
  90. 11 4
      services/data/excel/excel_info.go
  91. 3 90
      services/data/predict_edb_info.go
  92. 165 103
      services/data/predict_edb_info_rule.go
  93. 1720 0
      services/data/range_analysis/chart_info.go
  94. 555 0
      services/document_manage_service/document_manage_service.go
  95. 198 8
      services/elastic/elastic.go
  96. 42 0
      services/english_report.go
  97. 217 0
      services/eta_forum/chart_collect.go
  98. 88 0
      services/eta_forum/eta_forum_hub_lib.go
  99. 220 0
      services/excel/lucky_sheet.go
  100. 66 0
      services/excel_info.go

+ 1 - 1
.idea/vcs.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+    <mapping directory="" vcs="Git" />
   </component>
 </project>

+ 100 - 47
controllers/classify.go

@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"eta/eta_mobile/models"
+	"eta/eta_mobile/services"
 	"eta/eta_mobile/utils"
 )
 
@@ -12,8 +13,6 @@ type ClassifyController struct {
 
 // @Title 获取分类列表
 // @Description 获取分类列表
-// @Param   PageSize   query   int  true       "每页数据条数"
-// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   KeyWord   query   string  true       "检索关键词"
 // @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
 // @Param   HideDayWeek   query   int  false       "是否隐藏晨周报"
@@ -27,31 +26,54 @@ func (this *ClassifyController) ListClassify() {
 	}()
 
 	keyWord := this.GetString("KeyWord")
-	companyType := this.GetString("CompanyType")
-	hideDayWeek, _ := this.GetInt("HideDayWeek")
-
 	reqEnabled, _ := this.GetInt("Enabled", -1)
-	// 商家不隐藏晨周报
-	if utils.BusinessCode != utils.BusinessCodeRelease {
-		hideDayWeek = 0
-	}
+
 	enabled := -1
 	if reqEnabled == 1 {
 		enabled = reqEnabled
 	}
 
-	list, err := models.GetClassifyList(keyWord, companyType, hideDayWeek, enabled)
+	list, err := models.GetClassifyListByKeyword(keyWord, enabled)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		return
 	}
 
-	parentIds := make([]int, 0)
+	if keyWord != `` {
+		idMap := make(map[int]bool)
+
+		currParentClassifyIdList := make([]int, 0)
+		for _, v := range list {
+			idMap[v.Id] = true
+			if v.ParentId > 0 {
+				currParentClassifyIdList = append(currParentClassifyIdList, v.ParentId)
+			}
+		}
+
+		findList := list
+		list = make([]*models.ClassifyList, 0)
+
+		tmpList, tmpErr := services.GetParentClassifyListByParentIdList(currParentClassifyIdList)
+		if tmpErr != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取上级分类信息失败,Err:" + tmpErr.Error()
+			return
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.Id]; !ok {
+				list = append(list, v)
+			}
+		}
+
+		list = append(list, findList...)
+	}
+
+	classifyIdList := make([]int, 0)
 	for i := range list {
-		parentIds = append(parentIds, list[i].Id)
+		classifyIdList = append(classifyIdList, list[i].Id)
 	}
-	parentIdLen := len(parentIds)
+	parentIdLen := len(classifyIdList)
 	if parentIdLen == 0 {
 		resp := &models.ClassifyListResp{
 			List: list,
@@ -63,12 +85,12 @@ func (this *ClassifyController) ListClassify() {
 		return
 	}
 
-	// 获取一级分类-子目录列表
+	// 获取子目录列表
 	menuListMap := make(map[int][]*models.ClassifyMenu, 0)
 	var menuCond string
 	var menuPars []interface{}
 	menuCond += ` AND classify_id IN (` + utils.GetOrmInReplace(parentIdLen) + `)`
-	menuPars = append(menuPars, parentIds)
+	menuPars = append(menuPars, classifyIdList)
 	parentMenus, e := models.GetClassifyMenuList(menuCond, menuPars)
 	if e != nil {
 		br.Msg = "获取失败"
@@ -82,26 +104,13 @@ func (this *ClassifyController) ListClassify() {
 		menuListMap[parentMenus[i].ClassifyId] = append(menuListMap[parentMenus[i].ClassifyId], parentMenus[i])
 	}
 
-	// 获取子分类
-	children, e := models.GetClassifyChildByParentIds(parentIds, keyWord, enabled)
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取子分类失败"
-		return
-	}
-	childrenIds := make([]int, 0)
-	for i := range children {
-		childrenIds = append(childrenIds, children[i].Id)
-	}
-	childrenIdsLen := len(childrenIds)
-
-	// 获取二级分类-子目录关联
+	// 分类与子目录关联
 	relateMap := make(map[int]int, 0)
-	if childrenIdsLen > 0 {
+	{
 		var relateCond string
 		var relatePars []interface{}
-		relateCond += ` AND classify_id IN (` + utils.GetOrmInReplace(childrenIdsLen) + `)`
-		relatePars = append(relatePars, childrenIds)
+		relateCond += ` AND classify_id IN (` + utils.GetOrmInReplace(parentIdLen) + `)`
+		relatePars = append(relatePars, classifyIdList)
 		relates, e := models.GetClassifyMenuRelationList(relateCond, relatePars)
 		if e != nil {
 			br.Msg = "获取失败"
@@ -113,26 +122,29 @@ func (this *ClassifyController) ListClassify() {
 		}
 	}
 
-	// 二级分类
-	childrenMap := make(map[int][]*models.ClassifyItem, 0)
-	for i := range children {
-
-		if childrenMap[children[i].ParentId] == nil {
-			childrenMap[children[i].ParentId] = make([]*models.ClassifyItem, 0)
-		}
-		tmp := &models.ClassifyItem{
-			Classify:       *children[i],
-			ClassifyMenuId: relateMap[children[i].Id],
+	// 查询分类绑定的权限
+	permissionList, _ := models.GetAllPermissionMapping()
+	classifyPermissionMap := make(map[int][]int)
+	if len(permissionList) > 0 {
+		for _, v := range permissionList {
+			classifyPermissionMap[v.ClassifyId] = append(classifyPermissionMap[v.ClassifyId], v.ChartPermissionId)
 		}
-		childrenMap[children[i].ParentId] = append(childrenMap[children[i].ParentId], tmp)
 	}
+	// 遍历分类并绑定子目录和权限
+	for i, v := range list {
+		list[i].ClassifyMenuList = menuListMap[v.Id]
 
-	// 一级分类
-	for i := range list {
-		list[i].ClassifyMenuList = menuListMap[list[i].Id]
-		list[i].Child = childrenMap[list[i].Id]
+		list[i].ClassifyMenuId = relateMap[v.Id]
+		if permissionIds, ok := classifyPermissionMap[v.Id]; ok {
+			list[i].ChartPermissionIdList = permissionIds
+		}
 	}
 
+	// 先将分类列表排序
+	services.SortClassifyListBySortAndCreateTime(list)
+	// 接着转换结构
+	list = services.GetClassifyListTreeRecursive(list, 0)
+
 	resp := new(models.ClassifyListResp)
 	resp.List = list
 	br.Data = resp
@@ -140,3 +152,44 @@ func (this *ClassifyController) ListClassify() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// ClassifyPermission
+// @Title 获取分类权限列表
+// @Description 获取分类权限列表
+// @Param   ClassifyId   query   int  true       "分类id"
+// @Success 200 {object} []models.ChartPermissionSearchKeyWordMapping
+// @router /permission_list [get]
+func (this *ClassifyController) ClassifyPermission() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	//获取报告分类详情
+	classifyInfo, err := models.GetClassifyById(classifyId)
+	if err != nil {
+		br.Msg = "获取分类信息失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	//获取报告分类权限列表
+	list, err := models.GetPermissionByClassifyId(classifyInfo.Id)
+	if err != nil {
+		br.Msg = "获取分类信息失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 72 - 0
controllers/data_manage/chart_common.go

@@ -7,9 +7,11 @@ import (
 	"eta/eta_mobile/controllers/data_manage/future_good"
 	"eta/eta_mobile/controllers/data_manage/line_equation"
 	"eta/eta_mobile/controllers/data_manage/line_feature"
+	"eta/eta_mobile/controllers/data_manage/range_analysis"
 	"eta/eta_mobile/models"
 	"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"
@@ -167,6 +169,17 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_RANGE_ANALYSIS:
+		resp, isOk, msg, errMsg := range_analysis.GetChartInfoDetailFromUniqueCode(chartInfo, isCache, sysUser)
+		if !isOk {
+			br.Msg = msg
+			br.ErrMsg = errMsg
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
 	default:
 		br.Msg = "错误的图表"
 		br.ErrMsg = "错误的图表"
@@ -216,3 +229,62 @@ 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, 30*time.Minute)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败"
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = token
+
+	return
+}

+ 83 - 3
controllers/data_manage/chart_info.go

@@ -2,6 +2,7 @@ package data_manage
 
 import (
 	"encoding/json"
+	"errors"
 	"eta/eta_mobile/controllers"
 	"eta/eta_mobile/models"
 	"eta/eta_mobile/models/data_manage"
@@ -9,6 +10,7 @@ import (
 	"eta/eta_mobile/models/system"
 	"eta/eta_mobile/services"
 	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/services/data/area_graph"
 	"eta/eta_mobile/services/data/data_manage_permission"
 	"eta/eta_mobile/services/data/excel"
 	etaTrialService "eta/eta_mobile/services/eta_trial"
@@ -1218,6 +1220,16 @@ func (this *ChartInfoController) ChartInfoDetail() {
 			}
 		}
 
+		// 面积图 面积堆积 数据处理
+		if chartInfo.ChartType == utils.CHART_TYPE_AREA {
+			err, errMsg = fillAreaGraphData(extraConfigStr, edbList)
+			if err != nil {
+				br.Msg = "面积图处理失败"
+				br.ErrMsg = errMsg
+				return
+			}
+		}
+
 		// 图表的指标来源
 		sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
 		chartInfo.ChartSource = strings.Join(sourceNameList, ",")
@@ -1249,6 +1261,60 @@ func (this *ChartInfoController) ChartInfoDetail() {
 	br.Data = resp
 }
 
+func fillAreaGraphData(extraConfigStr string, edbDataList []*data_manage.ChartEdbInfoMapping) (err error, errMsg string) {
+
+	var tmpConfig data_manage.AreaExtraConf
+	if extraConfigStr != `` {
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpConfig)
+		if err != nil {
+			errMsg = "面积图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		if tmpConfig.StandardEdbInfoId <= 0 {
+			utils.FileLog.Info(`面积图未开启面积堆积`)
+			return
+		}
+	}
+	if tmpConfig.IsHeap == 1 {
+		standardIndexMap := make(map[string]*data_manage.EdbDataList)
+		var startDate, endDate string
+		for _, v := range edbDataList {
+			// 判断是否为基准指标
+			if v.EdbInfoId == tmpConfig.StandardEdbInfoId {
+				if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+					startDate = dataList[0].DataTime
+					endDate = dataList[len(dataList)-1].DataTime
+					for _, dataObject := range dataList {
+						standardIndexMap[dataObject.DataTime] = dataObject
+					}
+				}
+				break
+			}
+		}
+		strategy, err := area_graph.CreateStrategy(tmpConfig.NullDealWay)
+		if err != nil {
+			return err, "创建空值处理器失败"
+		}
+		err = strategy.Deal(tmpConfig, edbDataList, standardIndexMap, startDate, endDate)
+		if err != nil {
+			return err, err.Error()
+		}
+
+		// 时间戳处理
+		for _, mapping := range edbDataList {
+			if dataList, ok := mapping.DataList.([]*data_manage.EdbDataList); ok {
+				for _, dataInfo := range dataList {
+					toFormatTime := utils.StringToFormatTime(dataInfo.DataTime, utils.FormatDate)
+					dataInfo.DataTimestamp = toFormatTime.UnixMilli()
+				}
+			}
+		}
+	}
+
+	return nil, ""
+}
+
 // PreviewChartInfoDetail
 // @Title 获取图表详情(预览)
 // @Description 获取图表详情接口(预览)
@@ -2153,7 +2219,7 @@ func (this *ChartInfoController) ChartInfoSearchByEs() {
 		showSysId = sysUser.AdminId
 	}
 
-	var searchList []*data_manage.ChartInfo
+	var searchList []*data_manage.ChartInfoMore
 	var total int64
 	var err error
 
@@ -2262,7 +2328,7 @@ func (this *ChartInfoController) ChartInfoSearchByEs() {
 
 		for _, v := range searchList {
 			tmp := new(data_manage.ChartInfoMore)
-			tmp.ChartInfo = *v
+			tmp.ChartInfo = v.ChartInfo
 			//判断是否需要展示英文标识
 			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
 				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
@@ -2272,7 +2338,10 @@ func (this *ChartInfoController) ChartInfoSearchByEs() {
 			if currClassify, ok := chartClassifyMap[v.ChartClassifyId]; ok {
 				tmp.HaveOperaAuth = data_manage_permission.CheckChartPermissionByPermissionIdList(v.IsJoinPermission, currClassify.IsJoinPermission, v.ChartInfoId, v.ChartClassifyId, permissionChartIdList, permissionClassifyIdList)
 			}
-
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
 			finalList = append(finalList, tmp)
 		}
 	}
@@ -2738,6 +2807,17 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		chartInfo.ChartClassify = append(chartInfo.ChartClassify, chartClassifyParent)
 	}
 	chartInfo.ChartClassify = append(chartInfo.ChartClassify, chartViewClassify)
+
+	// 面积图 面积堆积 数据处理
+	if chartInfo.ChartType == utils.CHART_TYPE_AREA {
+		err, errMsg = fillAreaGraphData(extraConfigStr, edbList)
+		if err != nil {
+			msg = "获取失败"
+			errMsg = "面积图面积堆积数据处理失败,Err:" + err.Error()
+			return
+		}
+	}
+
 	resp.EdbInfoList = edbList
 	//判断是否需要展示英文标识
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, chartInfo.Source, chartInfo.ChartType)

+ 6 - 2
controllers/data_manage/correlation/correlation_chart_info.go

@@ -1795,7 +1795,7 @@ func (this *CorrelationChartInfoController) SearchByEs() {
 		sourceList = append(sourceList, source)
 	}
 
-	var searchList []*data_manage.ChartInfo
+	var searchList []*data_manage.ChartInfoMore
 	var total int64
 	var err error
 
@@ -1848,13 +1848,17 @@ func (this *CorrelationChartInfoController) SearchByEs() {
 
 		for _, v := range searchList {
 			tmp := new(data_manage.ChartInfoMore)
-			tmp.ChartInfo = *v
+			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)
 		}
 	}

+ 6 - 2
controllers/data_manage/cross_variety/chart_info.go

@@ -1433,7 +1433,7 @@ func (c *ChartInfoController) SearchByEs() {
 
 	sourceList := []int{utils.CHART_SOURCE_CROSS_HEDGING}
 
-	var searchList []*data_manage.ChartInfo
+	var searchList []*data_manage.ChartInfoMore
 	var total int64
 	var err error
 
@@ -1486,13 +1486,17 @@ func (c *ChartInfoController) SearchByEs() {
 
 		for _, v := range searchList {
 			tmp := new(data_manage.ChartInfoMore)
-			tmp.ChartInfo = *v
+			tmp.ChartInfo = v.ChartInfo
 			// 图表数据权限
 			tmp.HaveOperaAuth = true
 			//判断是否需要展示英文标识
 			if _, ok := chartEdbMap[v.ChartInfoId]; ok {
 				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, []*data_manage.ChartEdbInfoMapping{}, v.Source, v.ChartType)
 			}
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
 			finalList = append(finalList, tmp)
 		}
 	}

+ 45 - 0
controllers/data_manage/edb_info.go

@@ -3416,6 +3416,15 @@ func (this *EdbInfoController) EdbInfoFilterByEs() {
 	for i := 0; i < edbInfoListLen; i++ {
 		edbInfoList[i].EdbNameAlias = edbInfoList[i].EdbName
 		classifyIdList = append(classifyIdList, edbInfoList[i].ClassifyId)
+		// 如果没有关键词,那么搜索结果字段取指标名,前端已统一用该字段显示搜索的列表内容
+		if keyWord == "" {
+			if this.Lang == utils.ZhLangVersion {
+				edbInfoList[i].SearchText = edbInfoList[i].EdbName
+			}
+			if this.Lang == utils.EnLangVersion {
+				edbInfoList[i].SearchText = edbInfoList[i].EdbNameEn
+			}
+		}
 	}
 
 	// 当前列表中的分类map
@@ -3895,6 +3904,42 @@ func (this *ChartInfoController) EdbInfoReplace() {
 		br.ErrMsg = "原指标频度为:" + oldEdbInfo.Frequency + " 替换指标频度为:" + newEdbInfo.Frequency + ",频度不同,不可进行替换操作!"
 		return
 	}
+	if oldEdbInfo.EdbInfoType == 1 || newEdbInfo.EdbInfoType == 1 {
+		br.Msg = "预测指标不允许替换"
+		br.ErrMsg = "预测指标不允许替换"
+		return
+	}
+
+	// 判断指标是否循环引用
+	if oldEdbInfo.EdbType == 2 {
+		// 查询该计算指标的所有相关指标,如果相关指标中包含新指标,则提示
+		hasRelation, e := data.CheckTwoEdbInfoRelation(oldEdbInfo, newEdbInfo)
+		if e != nil {
+			br.Msg = "替换失败!"
+			br.ErrMsg = "查询指标引用关系失败,Err:" + e.Error()
+			return
+		}
+		if hasRelation {
+			br.Msg = "原指标与替换指标存在引用关系,不允许替换"
+			br.ErrMsg = "原指标与替换指标存在引用关系,不允许替换"
+			return
+		}
+	}
+
+	if newEdbInfo.EdbType == 2 {
+		// 查询该计算指标的所有相关指标,如果相关指标中包含新指标,则提示
+		hasRelation, e := data.CheckTwoEdbInfoRelation(newEdbInfo, oldEdbInfo)
+		if e != nil {
+			br.Msg = "替换失败!"
+			br.ErrMsg = "查询指标引用关系失败,Err:" + e.Error()
+			return
+		}
+		if hasRelation {
+			br.Msg = "原指标与替换指标存在引用关系,不允许替换"
+			br.ErrMsg = "原指标与替换指标存在引用关系,不允许替换"
+			return
+		}
+	}
 
 	sysAdminId := sysUser.AdminId
 	//replaceChartTotal, replaceCalculateTotal, err := data.EdbInfoReplace(oldEdbInfo, newEdbInfo, sysAdminId, sysUser.RealName)

+ 338 - 0
controllers/data_manage/excel/balance_table.go

@@ -1,6 +1,7 @@
 package excel
 
 import (
+	"archive/zip"
 	"encoding/json"
 	"errors"
 	"eta/eta_mobile/models"
@@ -11,8 +12,12 @@ import (
 	"eta/eta_mobile/services/data"
 	"eta/eta_mobile/services/data/data_manage_permission"
 	excelService "eta/eta_mobile/services/data/excel"
+	excel2 "eta/eta_mobile/services/excel"
 	"eta/eta_mobile/utils"
 	"fmt"
+	"github.com/tealeg/xlsx"
+	"io/ioutil"
+	"os"
 	"time"
 )
 
@@ -239,3 +244,336 @@ func refreshBalanceTable(excelDetail response.ExcelInfoDetail, lang string) (err
 	}
 	return
 }
+
+// GetChildTable
+// @Title 获取子表
+// @Description 获取子表
+// @Param	request	body request.MixedTableCellDataReq true "type json string"
+// @router /excel_info/child_table [get]
+func (c *ExcelInfoController) GetChildTable() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	parentId, _ := c.GetInt("ParentId")
+	if parentId <= 0 {
+		br.Msg = "请选择父表"
+		return
+	}
+	list := make([]*excel.ExcelInfo, 0)
+	// 查询所有子表
+	childList, err := excel.GetChildExcelInfoByParentId(parentId)
+	if err != nil {
+		br.Msg = "查询子表失败"
+		return
+	}
+	if len(childList) > 0 {
+		list = childList
+	}
+
+	resp := &response.BalanceChildTableResp{List: list}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询成功"
+	br.Data = resp
+}
+
+// BalanceVersionList
+// @Title 查询平衡表版本号列表
+// @Description 查询平衡表版本号列表
+// @Param	request	body request.EditExcelInfoReq true "type json string"
+// @Success 200 {object} response.AddExcelInfoResp
+// @router /excel_info/balance/version [get]
+func (c *ExcelInfoController) BalanceVersionList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	excelInfoId, _ := c.GetInt("ExcelInfoId")
+	if excelInfoId <= 0 {
+		br.Msg = "请选择表格"
+		br.ErrMsg = "ExcelInfoId未传"
+		br.IsSendEmail = false
+		return
+	}
+	excelInfo, err := excel.GetExcelInfoById(excelInfoId)
+	if err != nil {
+		br.Msg = "获取ETA表格失败"
+		br.ErrMsg = "获取ETA表格失败,Err:" + err.Error()
+		return
+	}
+	if excelInfo.Source != utils.BALANCE_TABLE {
+		br.Msg = "请选择平衡表"
+		return
+	}
+	if excelInfo.BalanceType != 0 && excelInfo.ParentId != 0 {
+		br.Msg = "请选择动态平衡表"
+		return
+	}
+	list := make([]*response.BalanceTableVersionListItem, 0)
+	list = append(list, &response.BalanceTableVersionListItem{
+		ExcelInfoId:    excelInfo.ExcelInfoId,
+		UniqueCode:     excelInfo.UniqueCode,
+		BalanceType:    excelInfo.BalanceType,
+		RelExcelInfoId: excelInfo.RelExcelInfoId,
+		VersionName:    "动态表", //todo 有个默认的版本名称
+	})
+	//查询动态表所有的子表,并复制为静态表
+	condition := " AND rel_excel_info_id=? AND parent_id = 0 AND balance_type = 1 "
+	var pars []interface{}
+	pars = append(pars, excelInfoId)
+
+	staticList, err := excel.GetNoContentExcelInfoListByConditionNoPage(condition, pars)
+	if err != nil {
+		br.Msg = "获取子表失败"
+		br.ErrMsg = fmt.Sprintf("获取子表失败 %s", err.Error())
+		return
+	}
+
+	for _, staticInfo := range staticList {
+		tmp := &response.BalanceTableVersionListItem{
+			ExcelInfoId:    staticInfo.ExcelInfoId,
+			UniqueCode:     staticInfo.UniqueCode,
+			BalanceType:    staticInfo.BalanceType,
+			RelExcelInfoId: staticInfo.RelExcelInfoId,
+			VersionName:    staticInfo.VersionName,
+		}
+		list = append(list, tmp)
+	}
+	ret := &response.BalanceTableVersionListResp{List: list}
+	br.Data = ret
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// GetBalanceChartList
+// @Title 获取平衡表表关联的图表
+// @Description 获取平衡表表关联的图表
+// @Param	request	body request.MixedTableCellDataReq true "type json string"
+// @router /excel_info/balance/chart_list [get]
+func (c *ExcelInfoController) GetBalanceChartList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	excelInfoId, _ := c.GetInt("ExcelInfoId")
+	if excelInfoId <= 0 {
+		br.Msg = "请选择平衡表"
+		return
+	}
+	// 查询所有子表
+	excelInfo, err := excel.GetExcelInfoById(excelInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "表格不存在"
+			return
+		}
+		br.Msg = "查询子表失败"
+		br.ErrMsg = "查询子表失败,Err:" + err.Error()
+		return
+	}
+	list := make([]*data_manage.BalanceChartInfoDetailResp, 0)
+	chartInfoList, mappingListMap, dataListMap, err, errMsg := excelService.GetBalanceExcelChartList(excelInfo, "")
+	if err != nil {
+		if errMsg != "" {
+			br.Msg = errMsg
+			br.ErrMsg = err.Error()
+			return
+		} else {
+			br.Msg = "查询图表失败"
+			br.ErrMsg = "查询图表失败,Err:" + err.Error()
+		}
+		return
+	}
+	for _, chartInfo := range chartInfoList {
+		mappingList, ok := mappingListMap[chartInfo.ChartInfoId]
+		if !ok {
+			br.Msg = "未找到图表关联的指标信息"
+			return
+		}
+		var chartInfoResp *data_manage.ChartInfoDetailResp
+		chartInfoResp, err, errMsg = data.GetBalanceExcelChartDetail(chartInfo, mappingList, sysUser, dataListMap)
+		if err != nil {
+			br.Msg = "查询图表详情失败"
+			br.ErrMsg = "查询图表详情失败,Err:" + err.Error()
+			return
+		}
+		chartEdbList := make([]*data_manage.ExcelChartEdbView, 0)
+		for _, v := range mappingList {
+			tmp := &data_manage.ExcelChartEdbView{
+				ExcelChartEdbId: v.ExcelChartEdbId,
+				DateSequenceStr: v.DateSequence,
+				DataSequenceStr: v.DataSequence,
+				FromTag:         v.FromTag,
+			}
+			chartEdbList = append(chartEdbList, tmp)
+		}
+
+		balanceChartInfoResp := &data_manage.BalanceChartInfoDetailResp{
+			ChartInfoDetailResp: chartInfoResp,
+			ExcelEdbList:        chartEdbList,
+		}
+		list = append(list, balanceChartInfoResp)
+	}
+
+	ret := &data_manage.BalanceTableChartListResp{List: list}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询成功"
+	br.Data = ret
+}
+
+func downloadBalanceTable(excelInfo *excel.ExcelInfo, lang string) (savePath, zipName string, uploadDir string, err error, errMsg string) {
+	dateDir := time.Now().Format("20060102")
+	randStr := time.Now().Format(utils.FormatDateTimeUnSpace)
+	uploadDir = "static/xls/" + dateDir + "/" + randStr
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		return
+	}
+	fileList := make([]string, 0)
+	if excelInfo.ParentId != 0 && excelInfo.BalanceType != 0 {
+		errMsg = "平衡表类型错误"
+		err = fmt.Errorf("平衡表类型错误 ")
+		return
+	}
+	//复制静态表
+	staticCondition := " AND parent_id = 0 AND balance_type = 1 AND rel_excel_info_id=? "
+	var staticPars []interface{}
+	staticPars = append(staticPars, excelInfo.ExcelInfoId)
+	excelList, err := excel.GetExcelInfoListByCondition(staticCondition, staticPars)
+	if err != nil {
+		errMsg = "获取表格失败"
+		err = fmt.Errorf("获取表格失败 %s", err.Error())
+		return
+	}
+	excelList = append(excelList, excelInfo)
+	for _, staticExcelInfo := range excelList {
+		cCondition := " AND parent_id = ?"
+		var cPars []interface{}
+		cPars = append(cPars, staticExcelInfo.ExcelInfoId)
+		childList, e := excel.GetExcelInfoListByCondition(cCondition, cPars)
+		if e != nil {
+			errMsg = "获取子表失败"
+			err = fmt.Errorf("获取子表失败 %s", err.Error())
+			return
+		}
+		xlsxFile := xlsx.NewFile()
+		fileName := staticExcelInfo.ExcelName + ".xlsx"
+		fpath := uploadDir + "/" + fileName
+		for _, childExcelInfo := range childList {
+			var result request.MixedTableReq
+			err = json.Unmarshal([]byte(childExcelInfo.Content), &result)
+			if err != nil {
+				errMsg = "获取失败"
+				err = fmt.Errorf("表格json转结构体失败,Err:" + err.Error())
+				return
+			}
+			newResult, er, msg := excelService.GetMixedTableCellData(result, lang)
+			if er != nil {
+				err = er
+				errMsg = msg
+				return
+			}
+			tableData, er := excel2.GetTableDataByMixedTableData(newResult, false)
+			if er != nil {
+				errMsg = "获取失败"
+				err = fmt.Errorf("转换成table失败,Err:" + err.Error())
+				return
+			}
+			tableData, err = excel2.HandleRuleToTableCell(childExcelInfo.ExcelInfoId, tableData)
+			if err != nil {
+				errMsg = "获取失败"
+				err = fmt.Errorf("处理条件格式管理规则失败,Err:%w", err)
+				return
+			}
+			// 将单个sheet的数据写入到excel
+			err = tableData.WriteExcelSheetData(xlsxFile, childExcelInfo.ExcelName)
+			if err != nil {
+				return
+			}
+		}
+		//处理excel文件
+		//return
+		err = xlsxFile.Save(fpath)
+		if err != nil {
+			return
+		}
+		fileList = append(fileList, fileName)
+	}
+	// 创建zip
+	zipName = excelInfo.ExcelName + ".zip"
+
+	savePath = uploadDir + "/" + zipName
+	fmt.Println(savePath)
+	zipFile, err := os.Create(savePath)
+	if err != nil {
+		return
+	}
+	zipWriter := zip.NewWriter(zipFile)
+	// 生成zip过程中报错关闭
+	defer func() {
+		if err != nil {
+			zipWriter.Close()
+			zipFile.Close()
+		}
+		//os.Remove(savePath)
+	}()
+	//写入zip
+	for i := 0; i < len(fileList); i++ {
+		ioWriter, e := zipWriter.Create(fileList[i])
+		if e != nil {
+			err = fmt.Errorf("创建zip失败,Err:" + e.Error())
+			if os.IsPermission(e) {
+				fmt.Println("权限不足: ", e)
+				return
+			}
+			return
+		}
+		fullPath := uploadDir + "/" + fileList[i]
+		content, e := ioutil.ReadFile(fullPath)
+		if e != nil {
+			err = fmt.Errorf("读取文件失败,Err:" + e.Error())
+			return
+		}
+		ioWriter.Write(content)
+	}
+	// 生成zip后关闭,否则下载文件会损坏
+	zipWriter.Close()
+	zipFile.Close()
+	//this.Ctx.Output.Download(downLoadnFilePath, downloadFileName)
+	return
+}

+ 366 - 12
controllers/data_manage/excel/excel_info.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_mobile/controllers"
 	"eta/eta_mobile/models"
 	"eta/eta_mobile/models/data_manage"
+	excelPermissionModel "eta/eta_mobile/models/data_manage/data_manage_permission"
 	excel3 "eta/eta_mobile/models/data_manage/excel"
 	"eta/eta_mobile/models/data_manage/excel/request"
 	"eta/eta_mobile/models/data_manage/excel/response"
@@ -13,6 +14,7 @@ import (
 	"eta/eta_mobile/services/data"
 	"eta/eta_mobile/services/data/data_manage_permission"
 	excel2 "eta/eta_mobile/services/data/excel"
+	"eta/eta_mobile/services/elastic"
 	"eta/eta_mobile/services/excel"
 	"eta/eta_mobile/utils"
 	"fmt"
@@ -347,6 +349,9 @@ func (c *ExcelInfoController) List() {
 	} else {
 		condition += " AND source = ? "
 		pars = append(pars, source)
+		if source == utils.BALANCE_TABLE { //平衡表的列表只显示动态表的一级表(不显示子表和静态表)
+			condition += " AND parent_id = 0 AND balance_type=0 "
+		}
 	}
 
 	// 筛选分类
@@ -386,9 +391,95 @@ func (c *ExcelInfoController) List() {
 	}
 	//只看我的
 	isShowMe, _ := c.GetBool("IsShowMe")
+	// 获取所有有权限的指标和分类
+	permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserExcelAndClassifyPermissionList(c.SysUser.AdminId, 0, 0)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+		return
+	}
+	hasCheck := make(map[int]bool)
 	if isShowMe {
-		condition += " AND sys_user_id = ? "
-		pars = append(pars, sysUser.AdminId)
+		if source == utils.BALANCE_TABLE { //平衡表的,显示同时需要显示协作人相关的图表
+			//找到当前协作人相关的表格ID
+			obj := new(excel3.ExcelWorker)
+			existList, err := obj.GetBySysUserId(sysUser.AdminId)
+			if err != nil {
+				br.Msg = "获取表格协作人失败!"
+				br.ErrMsg = "获取表格协作人失败,Err:" + err.Error()
+				return
+			}
+			var excelIds []int
+			newCondition := condition
+			newPars := pars
+			if len(existList) > 0 {
+				for _, v := range existList {
+					excelIds = append(excelIds, v.ExcelInfoId)
+				}
+				newCondition += fmt.Sprintf(` AND  ( excel_info_id IN (%s)  or sys_user_id = ?)`, utils.GetOrmInReplace(len(excelIds)))
+				newPars = append(newPars, excelIds, sysUser.AdminId)
+			} else {
+				newCondition += ` AND  sys_user_id = ? `
+				newPars = append(newPars, sysUser.AdminId)
+			}
+
+			//获取表格信息
+			tmpList, e := excel3.GetNoContentExcelListByConditionNoPage(newCondition, newPars)
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				br.Success = true
+				br.Msg = "获取表格信息失败"
+				br.ErrMsg = "获取表格信息失败,Err:" + e.Error()
+				return
+			}
+			classifyIdListTmp := make([]int, 0)
+			for _, v := range tmpList {
+				classifyIdListTmp = append(classifyIdListTmp, v.ExcelClassifyId)
+			}
+			classifyMap := make(map[int]*excel3.ExcelClassify)
+
+			// 分类信息
+			if len(classifyIdListTmp) > 0 {
+				classifyListTmp, e := excel3.GetClassifyByIdList(classifyIdListTmp)
+				if e != nil {
+					br.Msg = "获取表格分类信息失败"
+					br.ErrMsg = "获取表格分类列表数据失败,Err:" + e.Error()
+					return
+				}
+				for _, v := range classifyListTmp {
+					classifyMap[v.ExcelClassifyId] = v
+				}
+			}
+			excelIds = make([]int, 0)
+			for _, v := range tmpList {
+				// 数据权限
+				if classifyInfo, ok := classifyMap[v.ExcelClassifyId]; ok {
+					v.HaveOperaAuth = data_manage_permission.CheckExcelPermissionByPermissionIdList(v.IsJoinPermission, classifyInfo.IsJoinPermission, v.ExcelInfoId, v.ExcelClassifyId, permissionEdbIdList, permissionClassifyIdList)
+					if v.HaveOperaAuth {
+						excelIds = append(excelIds, v.ExcelInfoId)
+					}
+					hasCheck[v.ExcelInfoId] = v.HaveOperaAuth
+				}
+			}
+			if len(excelIds) > 0 {
+				condition += fmt.Sprintf(` AND  excel_info_id IN (%s)`, utils.GetOrmInReplace(len(excelIds)))
+				pars = append(pars, excelIds)
+			} else {
+				list := make([]*excel3.MyExcelInfoList, 0)
+				resp := response.ExcelListResp{
+					Paging: page,
+					List:   list,
+				}
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				br.Data = resp
+				return
+			}
+
+		} else {
+			condition += " AND sys_user_id = ? "
+			pars = append(pars, sysUser.AdminId)
+		}
 	}
 	//获取表格信息
 	list, err := excel3.GetNoContentExcelListByCondition(condition, pars, startSize, pageSize)
@@ -408,6 +499,7 @@ func (c *ExcelInfoController) List() {
 		classifyIdList := make([]int, 0)
 		for _, v := range list {
 			classifyIdList = append(classifyIdList, v.ExcelClassifyId)
+
 		}
 		classifyMap := make(map[int]*excel3.ExcelClassify)
 
@@ -423,18 +515,32 @@ func (c *ExcelInfoController) List() {
 				classifyMap[v.ExcelClassifyId] = v
 			}
 		}
-		// 获取所有有权限的指标和分类
-		permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserExcelAndClassifyPermissionList(c.SysUser.AdminId, 0, 0)
-		if err != nil {
-			br.Msg = "获取失败"
-			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
-			return
-		}
 
-		for _, v := range list {
+		for k, v := range list {
 			// 数据权限
-			if classifyInfo, ok := classifyMap[v.ExcelClassifyId]; ok {
-				v.HaveOperaAuth = data_manage_permission.CheckExcelPermissionByPermissionIdList(v.IsJoinPermission, classifyInfo.IsJoinPermission, v.ExcelInfoId, v.ExcelClassifyId, permissionEdbIdList, permissionClassifyIdList)
+			if authCheck, ok1 := hasCheck[v.ExcelInfoId]; ok1 {
+				v.HaveOperaAuth = authCheck
+			} else {
+				if classifyInfo, ok := classifyMap[v.ExcelClassifyId]; ok {
+					v.HaveOperaAuth = data_manage_permission.CheckExcelPermissionByPermissionIdList(v.IsJoinPermission, classifyInfo.IsJoinPermission, v.ExcelInfoId, v.ExcelClassifyId, permissionEdbIdList, permissionClassifyIdList)
+				}
+			}
+			if v.Source == utils.BALANCE_TABLE {
+				//处理按钮权限和编辑状态
+				markStatus, err := services.UpdateExcelEditMark(v.ExcelInfoId, sysUser.AdminId, 2, sysUser.RealName)
+				if err != nil {
+					br.Msg = "查询标记状态失败"
+					br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+					return
+				}
+				if markStatus.Status == 0 {
+					list[k].CanEdit = true
+				} else {
+					list[k].Editor = markStatus.Editor
+				}
+
+				// excel表格按钮权限
+				list[k].Button = excel2.GetBalanceExcelInfoOpButton(sysUser.AdminId, v.SysUserId, v.HaveOperaAuth, v.ExcelInfoId)
 			}
 		}
 
@@ -2195,6 +2301,27 @@ func (c *ExcelInfoController) Download() {
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
 			return
 		}
+	case utils.BALANCE_TABLE: // 混合表格
+		savePath, fileName, uploadDir, err, errMsg := downloadBalanceTable(excelInfo, c.Lang)
+		if err != nil {
+			br.Msg = "下载失败"
+			if errMsg != `` {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
+			return
+		}
+		defer func() {
+			_ = os.RemoveAll(uploadDir)
+		}()
+		// todo 删除文件
+		c.Ctx.Output.Download(savePath, fileName)
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = excelInfo
+		return
+
 	}
 
 	downloadFilePath, err := tableData.ToExcel()
@@ -2631,3 +2758,230 @@ func (c *ExcelInfoController) GetEdbSource() {
 	br.Ret = 200
 	br.Success = true
 }
+
+// SearchByEs
+// @Title ES搜索
+// @Description ES搜索
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   Source   query   int  true       "格来源,1:excel插件的表格,2:自定义表格,3:混合表格,默认:1"
+// @Param   IsShowMe   query   bool  false       "是否只看我的,true、false"
+// @Param   IsShare   query   bool  false       "是否只看我的,true、false"
+// @Success 200 {object} response.ExcelListResp
+// @router /excel_info/search_by_es [get]
+func (c *ExcelInfoController) SearchByEs() {
+	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
+	}
+
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	source, _ := c.GetInt("Source")
+	if source <= 0 {
+		source = utils.EXCEL_DEFAULT
+	}
+	isShowMe, _ := c.GetBool("IsShowMe")
+	isShare, _ := c.GetBool("IsShare")
+	keyword := c.GetString("KeyWord")
+	if keyword == `` {
+		keyword = c.GetString("Keyword")
+	}
+
+	var total, startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize15
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	// 平衡表的查询条件
+	var condBalance string
+	var parsBalance []interface{}
+	if source == utils.BALANCE_TABLE {
+		condBalance += ` AND source = ? AND parent_id = 0 AND balance_type = 0` // 只显示动态表的一级表(不显示子表和静态表)
+		parsBalance = append(parsBalance, source)
+	}
+
+	// 可见性过滤
+	var queryIds, exceptIds []int
+	{
+		unauthorized, e := excelPermissionModel.GetExcelInfoDataNoPermissionByUserId(sysUser.AdminId, source)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取无权限表格失败, %v", e)
+			return
+		}
+		if len(unauthorized) > 0 {
+			for _, v := range unauthorized {
+				id, _ := strconv.Atoi(v.DataId)
+				if id == 0 {
+					continue
+				}
+				exceptIds = append(exceptIds, id)
+			}
+		}
+	}
+
+	// 自定义分析表
+	var queryAdminId int
+	if source == utils.CUSTOM_ANALYSIS_TABLE {
+		// 自定义分析共享表格
+		if isShare {
+			var kw string
+			if keyword != "" {
+				kw = fmt.Sprint("%", kw, "%")
+			}
+			// 查询我分享的/分享给我的表格
+			excels, e := excelPermissionModel.GetAdminAuthExcelInfoPermission(source, sysUser.AdminId, kw)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取我分享的/分享给我的表格失败, %v", e)
+				return
+			}
+			var excelIds []int
+			for _, v := range excels {
+				id := int(v.ExcelInfoId)
+				if !utils.InArrayByInt(excelIds, id) {
+					excelIds = append(excelIds, id)
+					continue
+				}
+			}
+			if len(excelIds) == 0 {
+				list := make([]*excel3.SearchExcelInfo, 0)
+				resp := response.SearchExcelListResp{
+					Paging: page,
+					List:   list,
+				}
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				br.Data = resp
+				return
+			}
+			queryIds = excelIds
+		} else {
+			// 非共享只看我的
+			isShowMe = true
+		}
+	}
+
+	// 获取所有有权限的指标和分类
+	permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserExcelAndClassifyPermissionList(c.SysUser.AdminId, 0, 0)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+		return
+	}
+	hasCheck := make(map[int]bool)
+	if isShowMe {
+		// 平衡表查询有权限的表格IDs
+		if source == utils.BALANCE_TABLE {
+			excelIds, e := services.GetBalanceExcelIdsByAdminId(sysUser.AdminId, condBalance, parsBalance, permissionEdbIdList, permissionClassifyIdList)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取平衡表有权限的表格IDs失败, %v", e)
+				return
+			}
+			if len(excelIds) == 0 {
+				list := make([]*excel3.SearchExcelInfo, 0)
+				resp := response.SearchExcelListResp{
+					Paging: page,
+					List:   list,
+				}
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				br.Data = resp
+				return
+			}
+			queryIds = excelIds
+		} else {
+			queryAdminId = sysUser.AdminId
+		}
+	}
+
+	// es搜索表格
+	t, list, e := elastic.SearchExcelInfoData(utils.EsExcelIndexName, keyword, source, queryAdminId, queryIds, exceptIds, startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("搜索表格失败, %v", e)
+		return
+	}
+	total = int(t)
+
+	if len(list) > 0 {
+		classifyIdList := make([]int, 0)
+		for _, v := range list {
+			classifyIdList = append(classifyIdList, v.ExcelClassifyId)
+
+		}
+		classifyMap := make(map[int]*excel3.ExcelClassify)
+
+		// 分类信息
+		{
+			classifyList, err := excel3.GetClassifyByIdList(classifyIdList)
+			if err != nil {
+				br.Msg = "获取表格列表信息失败"
+				br.ErrMsg = "获取表格分类列表数据失败,Err:" + err.Error()
+				return
+			}
+			for _, v := range classifyList {
+				classifyMap[v.ExcelClassifyId] = v
+			}
+		}
+
+		for k, v := range list {
+			// 数据权限
+			if authCheck, ok1 := hasCheck[v.ExcelInfoId]; ok1 {
+				v.HaveOperaAuth = authCheck
+			} else {
+				if classifyInfo, ok := classifyMap[v.ExcelClassifyId]; ok {
+					v.HaveOperaAuth = data_manage_permission.CheckExcelPermissionByPermissionIdList(v.IsJoinPermission, classifyInfo.IsJoinPermission, v.ExcelInfoId, v.ExcelClassifyId, permissionEdbIdList, permissionClassifyIdList)
+				}
+			}
+			if v.Source == utils.BALANCE_TABLE {
+				// 处理按钮权限和编辑状态
+				markStatus, err := services.UpdateExcelEditMark(v.ExcelInfoId, sysUser.AdminId, 2, sysUser.RealName)
+				if err != nil {
+					br.Msg = "查询标记状态失败"
+					br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+					return
+				}
+				if markStatus.Status == 0 {
+					list[k].CanEdit = true
+				} else {
+					list[k].Editor = markStatus.Editor
+				}
+
+				// excel表格按钮权限
+				list[k].Button = excel2.GetBalanceExcelInfoOpButton(sysUser.AdminId, v.SysUserId, v.HaveOperaAuth, v.ExcelInfoId)
+			}
+		}
+	}
+
+	page = paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.SearchExcelListResp{
+		Paging: page,
+		List:   list,
+	}
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 78 - 53
controllers/data_manage/future_good/future_good_chart_info.go

@@ -923,7 +923,7 @@ func (this *FutureGoodChartInfoController) ChartEnInfoEdit() {
 	switch chartItem.Source {
 	case utils.CHART_SOURCE_FUTURE_GOOD:
 		err = data_manage.EditFutureGoodChartEnInfoAndEdbEnInfo(req.ChartInfoId, req.ChartNameEn, edbInfo.EdbInfoId, req.EdbNameEn, req.UnitEn)
-		if req.FutureGoodNameEn != `` {
+		/*if req.FutureGoodNameEn != `` {
 			futureGoodEdbInfoMapping, err := data_manage.GetFutureGoodEdbChartEdbMapping(chartItem.ChartInfoId)
 			if err != nil {
 				br.Msg = "修改失败"
@@ -959,7 +959,7 @@ func (this *FutureGoodChartInfoController) ChartEnInfoEdit() {
 				}
 				v.Update([]string{"FutureGoodEdbNameEn"})
 			}
-		}
+		}*/
 	case utils.CHART_SOURCE_FUTURE_GOOD_PROFIT:
 		err = data_manage.EditFutureGoodProfitChartEnInfoAndEdbEnInfo(req.ChartInfoId, req.ChartNameEn, edbInfo.EdbInfoId, req.EdbNameEn, req.UnitEn, req.ProfitNameEn)
 	default:
@@ -1400,6 +1400,29 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 				}
 			}
 		}
+		if v.Source == 0 {
+			name := strings.Split(v.EdbName, "(")
+			if barConfig.FutureGoodEdbName != "" {
+				name[0] = barConfig.FutureGoodEdbName
+			}
+			v.EdbName = name[0]
+			if len(name) > 1 {
+				v.EdbName = v.EdbName + "(" + name[1]
+			}
+			//英文
+			// 编译正则表达式,匹配一个或多个数字
+
+			if v.EdbNameEn != "" {
+				name = strings.Split(v.EdbNameEn, "(")
+				if barConfig.FutureGoodEdbNameEn != "" {
+					name[0] = barConfig.FutureGoodEdbNameEn
+				}
+				v.EdbNameEn = name[0]
+				if len(name) > 1 {
+					v.EdbNameEn = v.EdbNameEn + "(" + name[1]
+				}
+			}
+		}
 	}
 	if len(warnEdbList) > 0 {
 		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
@@ -1731,6 +1754,29 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 				}
 			}
 		}
+		if v.Source == 0 {
+			name := strings.Split(v.EdbName, "(")
+			if barConfig.FutureGoodEdbName != "" {
+				name[0] = barConfig.FutureGoodEdbName
+			}
+			v.EdbName = name[0]
+			if len(name) > 1 {
+				v.EdbName = v.EdbName + "(" + name[1]
+			}
+			//英文
+			// 编译正则表达式,匹配一个或多个数字
+
+			if v.EdbNameEn != "" {
+				name = strings.Split(v.EdbNameEn, "(")
+				if barConfig.FutureGoodEdbNameEn != "" {
+					name[0] = barConfig.FutureGoodEdbNameEn
+				}
+				v.EdbNameEn = name[0]
+				if len(name) > 1 {
+					v.EdbNameEn = v.EdbNameEn + "(" + name[1]
+				}
+			}
+		}
 	}
 	if len(warnEdbList) > 0 {
 		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
@@ -2306,7 +2352,7 @@ func (this *FutureGoodChartInfoController) ChartInfoSearchByEs() {
 		showSysId = sysUser.AdminId
 	}
 
-	var searchList []*data_manage.ChartInfo
+	var searchList []*data_manage.ChartInfoMore
 	var total int64
 	var err error
 
@@ -2359,13 +2405,17 @@ func (this *FutureGoodChartInfoController) ChartInfoSearchByEs() {
 
 		for _, v := range searchList {
 			tmp := new(data_manage.ChartInfoMore)
-			tmp.ChartInfo = *v
+			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)
 		}
 	}
@@ -3272,6 +3322,22 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 			}
 			xDataList := barConfig.XDataList
 			length := len(xDataList)
+			oldFutureEdbName := barConfig.FutureGoodEdbName
+			oldFutureEdbNameEn := barConfig.FutureGoodEdbNameEn
+			if oldFutureEdbName == "" {
+				futureGoodEdbInfoMapping, err := data_manage.GetFutureGoodEdbChartEdbMapping(chartItem.ChartInfoId)
+				if err != nil {
+					br.Msg = "修改失败"
+					br.ErrMsg = "获取图表现货价格指标信息失败,指标信息失败,Err:" + err.Error()
+					return
+				}
+				list, _ := future_good.GetFutureGoodEdbInfoListByParentId(futureGoodEdbInfoMapping.EdbInfoId)
+				for _, v := range list {
+					oldFutureEdbName = v.FutureGoodEdbName
+					oldFutureEdbNameEn = v.FutureGoodEdbNameEn
+					break
+				}
+			}
 			switch this.Lang {
 			case utils.EnLangVersion:
 				for k, v := range req.XDataList {
@@ -3285,6 +3351,10 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 						}
 					}
 				}
+				if req.FutureGoodName != `` {
+					barConfig.FutureGoodEdbNameEn = req.FutureGoodName
+					barConfig.FutureGoodEdbName = oldFutureEdbName
+				}
 			default:
 				for k, v := range req.XDataList {
 					v = strings.TrimPrefix(v, " ")
@@ -3297,6 +3367,10 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 						}
 					}
 				}
+				if req.FutureGoodName != `` {
+					barConfig.FutureGoodEdbName = req.FutureGoodName
+					barConfig.FutureGoodEdbNameEn = oldFutureEdbNameEn
+				}
 			}
 			barConfig.XDataList = xDataList
 			barConfigByte, e := json.Marshal(barConfig)
@@ -3308,55 +3382,6 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 			chartItem.BarConfig = string(barConfigByte)
 		}
 		err = data_manage.EditBaseFutureGoodChartInfoAndEdbEnInfo(chartItem, &req, this.Lang)
-		if req.FutureGoodName != `` {
-			futureGoodEdbInfoMapping, err := data_manage.GetFutureGoodEdbChartEdbMapping(chartItem.ChartInfoId)
-			if err != nil {
-				br.Msg = "修改失败"
-				br.ErrMsg = "获取图表现货价格指标信息失败,指标信息失败,Err:" + err.Error()
-				return
-			}
-			futureGoodEdbInfo, err := future_good.GetFutureGoodEdbInfo(futureGoodEdbInfoMapping.EdbInfoId)
-			if err != nil {
-				if err.Error() == utils.ErrNoRow() {
-					br.Msg = "图表不存在!"
-					br.ErrMsg = "图表指标不存在,futureGoodEdbInfo:" + strconv.Itoa(futureGoodEdbInfo.FutureGoodEdbInfoId)
-					return
-				} else {
-					br.Msg = "获取图表信息失败!"
-					br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
-					return
-				}
-			}
-			if futureGoodEdbInfo == nil {
-				br.Msg = "期货商品指标不存在!"
-				br.ErrMsg = "期货商品指标不存在,futureGoodEdbInfo:" + strconv.Itoa(futureGoodEdbInfo.FutureGoodEdbInfoId)
-				return
-			}
-
-			list, _ := future_good.GetFutureGoodEdbInfoListByParentId(futureGoodEdbInfo.FutureGoodEdbInfoId)
-			for _, v := range list {
-				switch this.Lang {
-				case utils.EnLangVersion:
-					if v.FutureGoodEdbNameEn == `` {
-						v.FutureGoodEdbNameEn = strings.TrimPrefix(req.FutureGoodName, " ")
-						v.FutureGoodEdbNameEn = strings.TrimSuffix(req.FutureGoodName, " ")
-					} else {
-						v.FutureGoodEdbNameEn = strings.TrimPrefix(strings.Replace(v.FutureGoodEdbNameEn, v.FutureGoodEdbNameEn, req.FutureGoodName, -1), " ")
-						v.FutureGoodEdbNameEn = strings.TrimSuffix(req.FutureGoodName, " ")
-					}
-					v.Update([]string{"FutureGoodEdbNameEn"})
-				default:
-					if v.FutureGoodEdbName == `` {
-						v.FutureGoodEdbName = strings.TrimPrefix(req.FutureGoodName, " ")
-						v.FutureGoodEdbName = strings.TrimSuffix(req.FutureGoodName, " ")
-					} else {
-						v.FutureGoodEdbName = strings.TrimPrefix(strings.Replace(v.FutureGoodEdbName, v.FutureGoodEdbName, req.FutureGoodName, -1), " ")
-						v.FutureGoodEdbName = strings.TrimSuffix(req.FutureGoodName, " ")
-					}
-					v.Update([]string{"FutureGoodEdbName"})
-				}
-			}
-		}
 	case utils.CHART_SOURCE_FUTURE_GOOD_PROFIT:
 		if len(req.XDataList) > 0 {
 			// 处理横轴名称

+ 6 - 2
controllers/data_manage/line_equation/line_chart_info.go

@@ -1586,7 +1586,7 @@ func (this *LineEquationChartInfoController) SearchByEs() {
 
 	sourceList := []int{utils.CHART_SOURCE_LINE_EQUATION}
 
-	var searchList []*data_manage.ChartInfo
+	var searchList []*data_manage.ChartInfoMore
 	var total int64
 	var err error
 
@@ -1639,13 +1639,17 @@ func (this *LineEquationChartInfoController) SearchByEs() {
 
 		for _, v := range searchList {
 			tmp := new(data_manage.ChartInfoMore)
-			tmp.ChartInfo = *v
+			tmp.ChartInfo = v.ChartInfo
 			// 图表数据权限
 			tmp.HaveOperaAuth = true
 			//判断是否需要展示英文标识
 			if _, ok := chartEdbMap[v.ChartInfoId]; ok {
 				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, []*data_manage.ChartEdbInfoMapping{}, v.Source, v.ChartType)
 			}
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
 			finalList = append(finalList, tmp)
 		}
 	}

+ 6 - 2
controllers/data_manage/line_feature/chart_info.go

@@ -2717,7 +2717,7 @@ func (this *LineFeaturesChartInfoController) SearchByEs() {
 
 	sourceList := []int{utils.CHART_SOURCE_LINE_FEATURE_STANDARD_DEVIATION, utils.CHART_SOURCE_LINE_FEATURE_PERCENTILE, utils.CHART_SOURCE_LINE_FEATURE_FREQUENCY}
 
-	var searchList []*data_manage.ChartInfo
+	var searchList []*data_manage.ChartInfoMore
 	var total int64
 	var err error
 
@@ -2770,13 +2770,17 @@ func (this *LineFeaturesChartInfoController) SearchByEs() {
 
 		for _, v := range searchList {
 			tmp := new(data_manage.ChartInfoMore)
-			tmp.ChartInfo = *v
+			tmp.ChartInfo = v.ChartInfo
 			// 图表数据权限
 			tmp.HaveOperaAuth = true
 			//判断是否需要展示英文标识
 			if _, ok := chartEdbMap[v.ChartInfoId]; ok {
 				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, []*data_manage.ChartEdbInfoMapping{}, v.Source, v.ChartType)
 			}
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
 			finalList = append(finalList, tmp)
 		}
 	}

+ 18 - 106
controllers/data_manage/predict_edb_info.go

@@ -283,119 +283,31 @@ func (this *PredictEdbInfoController) List() {
 		br.ErrMsg = "查找预测指标关联信息失败"
 		return
 	}
-	if edbInfoItem.EdbType == 1 { //普通的预测指标
-		// 查找该预测指标配置
-		predictEdbConfList, err := data_manage.GetPredictEdbConfListById(edbInfoId)
+
+	//获取指标数据(实际已生成)
+	{
+		dataCount, dataList, err := data.GetPageData(edbInfoItem.EdbInfoId, edbInfoItem.Source, edbInfoItem.SubSource, edbInfoItem.LatestDate, startSize, pageSize)
 		if err != nil && err.Error() != utils.ErrNoRow() {
-			br.Msg = "获取失败"
-			br.ErrMsg = "获取预测指标配置信息失败,Err:" + err.Error()
-			return
-		}
-		if len(predictEdbConfList) == 0 {
-			br.Msg = "找不到该预测指标配置"
-			br.ErrMsg = "找不到该预测指标配置"
+			br.Msg = "获取指标信息失败"
+			br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
 			return
 		}
-		predictEdbConf := predictEdbConfList[0]
-
-		//预测数据的配置
-		edbInfoItem.RuleType = predictEdbConf.RuleType
-		edbInfoItem.FixedValue = predictEdbConf.FixedValue
+		page = paging.GetPaging(currentIndex, pageSize, dataCount)
+		edbInfoItem.DataList = dataList
+	}
 
-		// 来源指标
-		//sourceEdbInfoId := sourceEdbInfoCalculateMappingList[0].FromEdbInfoId
-		sourceEdbInfoId := predictEdbConf.SourceEdbInfoId
-		sourceEdbInfoItem, err := data_manage.GetEdbInfoById(sourceEdbInfoId)
+	// 第一页才需要 获取预测指标未来的数据
+	if currentIndex == 1 {
+		predictDataList, err = data_manage.GetAllEdbDataListData(edbInfoItem.EdbInfoId, edbInfoItem.Source, edbInfoItem.SubSource, edbInfoItem.LatestDate)
 		if err != nil {
-			br.Msg = "获取来源指标信息失败"
-			br.ErrMsg = "获取来源指标信息失败"
-			if err.Error() != utils.ErrNoRow() {
-				br.ErrMsg = "获取来源指标信息失败,Err:" + err.Error()
-			}
-			br.Success = true
+			br.Msg = "获取指标信息失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
 			return
 		}
-
-		//获取指标数据(实际已生成)
-		{
-			dataCount, dataList, err := data.GetPageData(sourceEdbInfoItem.EdbInfoId, sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, "", startSize, pageSize)
-			if err != nil && err.Error() != utils.ErrNoRow() {
-				br.Msg = "获取指标信息失败"
-				br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
-				return
-			}
-			page = paging.GetPaging(currentIndex, pageSize, dataCount)
-			edbInfoItem.DataList = dataList
-		}
-
-		// 第一页才需要 获取预测指标未来的数据
-		if currentIndex == 1 {
-			allDataList, err := data_manage.GetEdbDataList(sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, sourceEdbInfoItem.EdbInfoId, "", "")
-			if err != nil {
-				br.Msg = "获取失败"
-				br.Msg = "获取失败,Err:" + err.Error()
-				return
-			}
-			predictEdbConfDataList := make([]data_manage.PredictEdbConfAndData, 0)
-			for _, v := range predictEdbConfList {
-				predictEdbConfDataList = append(predictEdbConfDataList, data_manage.PredictEdbConfAndData{
-					ConfigId:         v.ConfigId,
-					PredictEdbInfoId: v.PredictEdbInfoId,
-					SourceEdbInfoId:  v.SourceEdbInfoId,
-					RuleType:         v.RuleType,
-					FixedValue:       v.FixedValue,
-					Value:            v.Value,
-					EndDate:          v.EndDate,
-					ModifyTime:       v.ModifyTime,
-					CreateTime:       v.CreateTime,
-					DataList:         make([]*data_manage.EdbDataList, 0),
-				})
-			}
-			tmpPredictDataList, _, _, err, _ := data.GetChartPredictEdbInfoDataListByConfList(predictEdbConfDataList, sourceEdbInfoItem.LatestDate, sourceEdbInfoItem.LatestDate, edbInfoItem.EndDate, sourceEdbInfoItem.Frequency, edbInfoItem.DataDateType, allDataList)
-			if err != nil {
-				br.Msg = "获取预测指标数据失败"
-				br.ErrMsg = "获取预测指标数据失败" + err.Error()
-				return
-			}
-			lenTmpPredictDataList := len(tmpPredictDataList)
-			if lenTmpPredictDataList > 0 {
-				for i := lenTmpPredictDataList - 1; i >= 0; i-- {
-					v := tmpPredictDataList[i]
-					predictDataList = append(predictDataList, &data_manage.EdbData{
-						EdbDataId: v.EdbDataId,
-						EdbInfoId: v.EdbInfoId,
-						DataTime:  v.DataTime,
-						Value:     v.Value,
-					})
-				}
-			}
-		}
-	} else {
-		//获取指标数据(实际已生成)
-		{
-			dataCount, dataList, err := data.GetPageData(edbInfoItem.EdbInfoId, edbInfoItem.Source, edbInfoItem.SubSource, edbInfoItem.LatestDate, startSize, pageSize)
-			if err != nil && err.Error() != utils.ErrNoRow() {
-				br.Msg = "获取指标信息失败"
-				br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
-				return
-			}
-			page = paging.GetPaging(currentIndex, pageSize, dataCount)
-			edbInfoItem.DataList = dataList
-		}
-
-		// 第一页才需要 获取预测指标未来的数据
-		if currentIndex == 1 {
-			predictDataList, err = data_manage.GetAllEdbDataListData(edbInfoItem.EdbInfoId, edbInfoItem.Source, edbInfoItem.SubSource, edbInfoItem.LatestDate)
-			if err != nil {
-				br.Msg = "获取指标信息失败"
-				br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
-				return
-			}
-			if err != nil {
-				br.Msg = "获取预测指标数据失败"
-				br.ErrMsg = "获取预测指标数据失败" + err.Error()
-				return
-			}
+		if err != nil {
+			br.Msg = "获取预测指标数据失败"
+			br.ErrMsg = "获取预测指标数据失败" + err.Error()
+			return
 		}
 	}
 

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

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

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

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

+ 479 - 0
controllers/document_manage/document_manage_controller.go

@@ -0,0 +1,479 @@
+// Package document_manage
+// @Author gmy 2024/9/19 14:09:00
+package document_manage
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/document_manage_model"
+	"eta/eta_mobile/services/document_manage_service"
+	"eta/eta_mobile/utils"
+	"strings"
+)
+
+// DocumentManageController 文档管理库
+type DocumentManageController struct {
+	controllers.BaseAuthController
+}
+
+// ValidateUser
+// 处理响应和校验
+func ValidateUser(this *DocumentManageController, br *models.BaseResponse) bool {
+	// 验证用户是否已登录
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return false
+	}
+
+	return true
+}
+
+// DocumentClassifyList
+// @Title 文档分类列表
+// @Description 文档分类列表
+// @Success 200 {object} []models.ClassifyVO
+// @router /document/classify/list [get]
+func (this *DocumentManageController) DocumentClassifyList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	sysUser := this.SysUser
+	classifyList, err := document_manage_service.DocumentClassifyList(sysUser.AdminId)
+	if err != nil {
+		br.Msg = "获取分类列表失败"
+		br.ErrMsg = "获取分类列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = classifyList
+	return
+}
+
+// DocumentCollectClassify
+// @Title 收藏/取消分类
+// @Description 收藏/取消分类
+// @Success 200 “操作成功”
+// @router /document/collect/classify [post]
+func (this *DocumentManageController) DocumentCollectClassify() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	sysUser := this.SysUser
+	type collectClassify struct {
+		ClassifyId int `json:"ClassifyId"`
+	}
+
+	var req *collectClassify
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	err := document_manage_service.DocumentCollectClassify(req.ClassifyId, sysUser.AdminId)
+	if err != nil {
+		br.Msg = "收藏分类失败"
+		br.ErrMsg = "收藏分类失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}
+
+// DocumentVarietyList
+// @Title 文档品种列表
+// @Description 文档品种列表
+// @Success 200 {object} []models.ChartPermission
+// @router /document/variety/list [get]
+func (this *DocumentManageController) DocumentVarietyList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	chartPermissionList, err := document_manage_service.DocumentVarietyList()
+	if err != nil {
+		br.Msg = "获取品种列表失败"
+		br.ErrMsg = "获取品种列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = chartPermissionList
+	return
+}
+
+// DocumentReportList
+// @Title 文档管理库报告列表
+// @Description 文档管理库报告列表
+// @Success 200 {object} document_manage_model.OutsideReportPage
+// @router /document/report/list [get]
+func (this *DocumentManageController) DocumentReportList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	// 文档类型 1-文档管理库 2-战研中心-pci
+	documentType, err := this.GetInt("DocumentType")
+	if err != nil {
+		br.Msg = "获取文档类型失败"
+		br.ErrMsg = "获取文档类型失败,Err:" + err.Error()
+		return
+	}
+	if documentType == 0 {
+		br.Msg = "文档类型不能为空"
+		br.ErrMsg = "文档类型不能为空"
+		return
+	}
+
+	chartPermissionIdString := this.GetString("ChartPermissionIdList")
+	var chartPermissionIdList []string
+	if strings.TrimSpace(chartPermissionIdString) != "" {
+		chartPermissionIdList = strings.Split(chartPermissionIdString, ",")
+	}
+
+	classifyIdString := this.GetString("ClassifyIdList")
+	var classifyIdList []string
+	if strings.TrimSpace(classifyIdString) != "" {
+		classifyIdList = strings.Split(classifyIdString, ",")
+	}
+
+	keyword := this.GetString("Keyword")
+	orderField := this.GetString("OrderField")
+	orderType := this.GetString("OrderType")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	documentReportPage, err := document_manage_service.DocumentReportList(this.SysUser.AdminId, documentType, chartPermissionIdList, classifyIdList, keyword, orderField, orderType, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取报告列表失败"
+		br.ErrMsg = "获取报告列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = documentReportPage
+	return
+}
+
+// RuiSiReportList
+// @Title 睿思报告列表
+// @Description 睿思报告列表
+// @Success 200 {object} models.ReportListResp
+// @router /document/rui/si/report/list [get]
+func (this *DocumentManageController) RuiSiReportList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	classifyIdListStr := this.GetString("ClassifyIdList")
+	var classifyIdList []string
+	if strings.TrimSpace(classifyIdListStr) != "" {
+		classifyIdList = strings.Split(classifyIdListStr, ",")
+	}
+	chartPermissionIdString := this.GetString("ChartPermissionIdList")
+	var chartPermissionIdList []string
+	if strings.TrimSpace(chartPermissionIdString) != "" {
+		chartPermissionIdList = strings.Split(chartPermissionIdString, ",")
+	}
+
+	keyword := this.GetString("Keyword")
+	orderField := this.GetString("OrderField")
+	orderType := this.GetString("OrderType")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	RuiSiReportPage, err := document_manage_service.RuiSiReportListV2(classifyIdList, chartPermissionIdList, keyword, orderField, orderType, this.SysUser.AdminId, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取报告列表失败"
+		br.ErrMsg = "获取报告列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = RuiSiReportPage
+	return
+}
+
+// DocumentRuiSiDetail
+// @Title 睿思报告详情
+// @Description 睿思报告详情
+// @Success 200 “获取成功”
+// @router /document/rui/si/detail [get]
+func (this *DocumentManageController) DocumentRuiSiDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	// 获取指标数据列表
+
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取报告ID失败"
+		br.ErrMsg = "获取报告ID失败,Err:" + err.Error()
+		return
+	}
+
+	reportDetail, err := document_manage_service.DocumentRuiSiDetail(reportId)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = &reportDetail
+	return
+}
+
+// DocumentSave
+// @Title 新建文档
+// @Description 新建文档
+// @Success 200 “操作成功”
+// @router /document/save [post]
+func (this *DocumentManageController) DocumentSave() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	sysUser := this.SysUser
+	var req *document_manage_model.OutsideReportBO
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	req.SysUserId = sysUser.AdminId
+	req.SysUserName = sysUser.AdminName
+	err := document_manage_service.DocumentSave(req)
+	if err != nil {
+		br.Msg = "保存文档失败"
+		br.ErrMsg = "保存文档失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}
+
+// DocumentDetail
+// @Title 文档详情
+// @Description 文档详情
+// @Success 200 “操作成功”
+// @router /document/detail [get]
+func (this *DocumentManageController) DocumentDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	// 获取指标数据列表
+
+	outsideReportId, err := this.GetInt("OutsideReportId")
+	if err != nil {
+		br.Msg = "获取报告ID失败"
+		br.ErrMsg = "获取报告ID失败,Err:" + err.Error()
+		return
+	}
+
+	reportDetail, err := document_manage_service.DocumentReportDetail(outsideReportId)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = &reportDetail
+	return
+}
+
+// DocumentUpdate
+// @Title 编辑文档
+// @Description 编辑文档
+// @Success 200 “操作成功”
+// @router /document/update [post]
+func (this *DocumentManageController) DocumentUpdate() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	var req *document_manage_model.OutsideReportBO
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	err := document_manage_service.DocumentUpdate(req)
+	if err != nil {
+		br.Msg = "修改文档失败"
+		br.ErrMsg = "修改文档失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}
+
+// DocumentDelete
+// @Title 删除文档
+// @Description 删除文档
+// @Success 200 “操作成功”
+// @router /document/delete [post]
+func (this *DocumentManageController) DocumentDelete() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	type DocumentDelete struct {
+		OutsideReportId int `json:"OutsideReportId"`
+	}
+
+	var req *DocumentDelete
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	err := document_manage_service.DocumentDelete(req.OutsideReportId)
+	if err != nil {
+		br.Msg = "删除文档失败"
+		br.ErrMsg = "删除文档失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}

+ 168 - 152
controllers/english_report/report.go

@@ -16,7 +16,6 @@ import (
 	"html"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 )
 
@@ -65,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 = "内容去除前后空格失败"
@@ -87,7 +88,7 @@ func (this *EnglishReportController) Add() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird, models.ReportOperateAdd)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -182,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 = "内容去除前后空格失败"
@@ -299,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})
@@ -449,143 +462,70 @@ func (this *EnglishReportController) ListReport() {
 			return
 		}
 	}
-	// 未群发邮件(包含推送邮件失败的)
-	if emailState == 1 {
-		failIds, e := models.GetHasFailEmailLogReportIds()
-		if e != nil {
-			br.Msg = "获取失败"
-			br.ErrMsg = "获取存在邮件推送失败记录的英文报告IDs失败, Err:" + e.Error()
-			return
-		}
-		condition += ` AND email_state = 0`
-		if len(failIds) > 0 {
-			condition += ` OR id IN (` + utils.GetOrmInReplace(len(failIds)) + `)`
-			pars = append(pars, failIds)
-		}
-	}
-	// 已群发邮件
-	if emailState == 2 {
-		successIds, e := models.GetSuccessEmailLogReportIds()
-		if e != nil {
-			br.Msg = "获取失败"
-			br.ErrMsg = "获取邮件推送记录均为成功的英文报告IDs失败, Err:" + e.Error()
-			return
+
+	// 群发邮件状态筛选
+	{
+		// 未群发邮件(包含推送邮件失败的)
+		if emailState == 1 {
+			condition += ` AND (email_state = 0 OR email_has_fail = 1) `
 		}
-		condition += ` AND email_state = 1`
-		if len(successIds) > 0 {
-			condition += ` AND id IN (` + utils.GetOrmInReplace(len(successIds)) + `)`
-			pars = append(pars, successIds)
+		// 已群发邮件
+		if emailState == 2 {
+			condition += ` AND email_state = 1 AND email_has_fail = 0 `
 		}
 	}
 
-	var total int
-	var errCount, errList, errOther error
 	var authOk bool
-	list := make([]*models.EnglishReportList, 0)
-	failMap := make(map[int]bool, 0)    // 有群发失败记录的研报
 	adminMap := make(map[int]string, 0) // 编辑中的研究员姓名
+	total, e := models.GetEnglishReportListCount(condition, pars, companyType)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文研报Count失败, Err: " + e.Error()
+		return
+	}
 
-	wg := sync.WaitGroup{}
-	wg.Add(3)
-
-	// 列表总数
-	go func() {
-		defer func() {
-			wg.Done()
-		}()
-
-		t, e := models.GetEnglishReportListCount(condition, pars, companyType)
-		if e != nil {
-			errCount = fmt.Errorf("获取英文研报Count失败, Err: %s", e.Error())
-			return
-		}
-		total = t
-	}()
-
-	// 列表数据
-	go func() {
-		defer func() {
-			wg.Done()
-		}()
-
-		// 限制一下富文本字段, 列表用不到
-		fieldArr := []string{
-			"id", "add_type", "classify_id_first", "classify_name_first", "classify_id_second", "classify_name_second", "title", "abstract", "author",
-			"frequency", "create_time", "modify_time", "state", "publish_time", "pre_publish_time", "stage", "msg_is_send", "report_code", "pv", "share_url",
-			"pv_email", "email_state", "from_report_id", "key_takeaways", "admin_id", "admin_real_name", "approve_time","detail_img_url","detail_pdf_url",
-		}
-		items, e := models.GetEnglishReportList(condition, pars, companyType, startSize, pageSize, fieldArr)
-		if e != nil {
-			errList = fmt.Errorf("获取英文研报列表失败, Err: %s", e.Error())
-			return
-		}
-		list = items
-	}()
-
-	// 群发权限/失败记录
-	go func() {
-		defer func() {
-			wg.Done()
-		}()
-
-		// 获取邮件配置-是否有权限群发
-		conf := new(models.EnglishReportEmailConf)
-		authKey := "english_report_email_conf"
-		confAuth, e := company.GetConfigDetailByCode(authKey)
-		if e != nil {
-			errOther = fmt.Errorf("获取群发邮件权限失败, Err: %s", e.Error())
-			return
-		}
-		if confAuth.ConfigValue == "" {
-			errOther = fmt.Errorf("群发邮件配置为空")
-			return
-		}
-		if e := json.Unmarshal([]byte(confAuth.ConfigValue), &conf); e != nil {
-			errOther = fmt.Errorf("群发邮件配置有误")
-			return
-		}
-		authArr := strings.Split(conf.SendAuthGroup, ",")
-		if utils.InArrayByStr(authArr, sysUser.RoleTypeCode) {
-			authOk = true
-		}
-
-		// 是否有群发邮件失败的记录,标记红点
-		failList, e := models.GetEnglishReportEmailLogFailList(0)
-		if e != nil {
-			errOther = fmt.Errorf("获取群发邮件记录失败, Err: %s", e.Error())
-			return
-		}
-		for i := range failList {
-			failMap[failList[i].ReportId] = true
-		}
-
-		// 获取admin, 用于匹配编辑中的研究员姓名
-		admins, e := system.GetSysAdminList("", make([]interface{}, 0), []string{"admin_id", "real_name"}, "")
-		if e != nil {
-			errOther = fmt.Errorf("获取系统用户列表失败, Err: %s", e.Error())
-			return
-		}
-		for _, a := range admins {
-			adminMap[a.AdminId] = a.RealName
-		}
-	}()
-	wg.Wait()
+	list, e := models.GetEnglishReportList(condition, pars, companyType, startSize, pageSize, []string{})
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文研报列表失败, Err: " + e.Error()
+		return
+	}
 
-	if errCount != nil {
+	// 获取邮件配置-是否有权限群发
+	conf := new(models.EnglishReportEmailConf)
+	authKey := "english_report_email_conf"
+	confAuth, e := company.GetConfigDetailByCode(authKey)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取群发邮件权限失败, Err: " + e.Error()
+		return
+	}
+	if confAuth.ConfigValue == "" {
 		br.Msg = "获取失败"
-		br.ErrMsg = errCount.Error()
+		br.ErrMsg = "群发邮件配置为空"
 		return
 	}
-	if errList != nil {
+	if e = json.Unmarshal([]byte(confAuth.ConfigValue), &conf); e != nil {
 		br.Msg = "获取失败"
-		br.ErrMsg = errList.Error()
+		br.ErrMsg = "群发邮件配置有误, Err: " + e.Error()
 		return
 	}
-	if errOther != nil {
+	authArr := strings.Split(conf.SendAuthGroup, ",")
+	if utils.InArrayByStr(authArr, sysUser.RoleTypeCode) {
+		authOk = true
+	}
+
+	// 获取admin, 用于匹配编辑中的研究员姓名
+	admins, e := system.GetSysAdminList("", make([]interface{}, 0), []string{"admin_id", "real_name"}, "")
+	if e != nil {
 		br.Msg = "获取失败"
-		br.ErrMsg = errOther.Error()
+		br.ErrMsg = "获取系统用户列表失败, Err: " + e.Error()
 		return
 	}
+	for _, a := range admins {
+		adminMap[a.AdminId] = a.RealName
+	}
+
 	// 查询分类信息
 	var classifyIdSecondSlice []int
 	for _, item := range list {
@@ -603,21 +543,17 @@ func (this *EnglishReportController) ListReport() {
 			classifyNameMap[v.Id] = v
 		}
 	}
-	for _, item := range list {
-		if item.State == 2 {
-			item.ShareUrl = "https://share.hzinsights.com/reportEn?code=" + item.ReportCode
-		}
-		item.EmailAuth = authOk
-		item.EmailHasFail = failMap[item.Id]
-
-		// 邮箱PV大于0的时候, 不展示最初版本的PV
-		if item.PvEmail > 0 {
-			item.Pv = 0
+	respList := make([]*models.EnglishReportList, 0)
+	for _, v := range list {
+		t := models.FormatEnglishReport2ListItem(v)
+		if v.State == 2 {
+			t.ShareUrl = "https://share.hzinsights.com/reportEn?code=" + v.ReportCode
 		}
+		t.EmailAuth = authOk
 
 		// 报告是否正在编辑中
 		var opUser models.MarkReportItem
-		key := fmt.Sprint(`crm:enReport:edit:`, item.Id)
+		key := fmt.Sprint(`crm:enReport:edit:`, v.Id)
 		opUserId, e := utils.Rc.RedisInt(key)
 		if e != nil {
 			str, te := utils.Rc.RedisString(key)
@@ -635,31 +571,36 @@ func (this *EnglishReportController) ListReport() {
 				editor = adminMap[opUserId]
 			}
 			ret.Status = 1
-			ret.Msg = fmt.Sprintf("当前%s正在编辑报告", editor)
+			if this.Lang == utils.EnLangVersion {
+				ret.Msg = fmt.Sprintf("%s is currently editing the report", editor)
+			} else {
+				ret.Msg = fmt.Sprintf("当前%s正在编辑报告", editor)
+			}
 			ret.Editor = editor
 		}
 		if ret.Status == 0 {
-			item.CanEdit = true
+			t.CanEdit = true
 		} else {
-			item.Editor = ret.Editor
+			t.Editor = ret.Editor
 		}
 
 		//处理分类名
-		if n, ok := classifyNameMap[item.ClassifyIdSecond]; ok {
+		if n, ok := classifyNameMap[v.ClassifyIdSecond]; ok {
 			if n.RootId == 0 {
-				item.FullClassifyName = strings.Join([]string{n.ParentName, n.ClassifyName}, "/")
+				t.FullClassifyName = strings.Join([]string{n.ParentName, n.ClassifyName}, "/")
 			} else {
-				item.FullClassifyName = strings.Join([]string{n.RootName, n.ParentName, n.ClassifyName}, "/")
+				t.FullClassifyName = strings.Join([]string{n.RootName, n.ParentName, n.ClassifyName}, "/")
 			}
-			item.ClassifyIdRoot = n.RootId
-			item.ClassifyNameRoot = n.RootName
+			t.ClassifyIdRoot = n.RootId
+			t.ClassifyNameRoot = n.RootName
 		}
+		respList = append(respList, t)
 	}
 
 	page := paging.GetPaging(currentIndex, pageSize, total)
 	resp := new(models.EnglishReportListResp)
 	resp.Paging = page
-	resp.List = list
+	resp.List = respList
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -736,7 +677,7 @@ func (this *EnglishReportController) PublishReport() {
 		}
 
 		// 根据审批开关及审批流判断当前报告状态
-		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, models.ReportOperatePublish)
+		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, 0, models.ReportOperatePublish)
 		if e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -755,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 {
 			// 从无审批切换为有审批, 状态重置
@@ -839,7 +781,7 @@ func (this *EnglishReportController) PrePublishReport() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond)
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, 0)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
@@ -858,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
@@ -898,7 +841,7 @@ func (this *EnglishReportController) PublishCancleReport() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, models.ReportOperateCancelPublish)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, 0, models.ReportOperateCancelPublish)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -1018,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")
@@ -1093,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})
@@ -1355,7 +1311,7 @@ func (this *EnglishReportController) SubmitApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateSubmitApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1378,7 +1334,7 @@ func (this *EnglishReportController) SubmitApprove() {
 	}
 
 	// 提交审批
-	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, sysUser.AdminId, sysUser.RealName)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "提交审批失败, Err: " + e.Error()
@@ -1446,7 +1402,7 @@ func (this *EnglishReportController) CancelApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateCancelApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1494,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 = "获取成功"
+}

+ 233 - 0
controllers/eta_forum/eta_forum.go

@@ -0,0 +1,233 @@
+package eta_forum
+
+import (
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/services/eta_forum"
+	"eta/eta_mobile/utils"
+)
+
+type EtaForumController struct {
+	controllers.BaseAuthController
+}
+
+// UserChartList
+// @Title 查询用户在eta社区中有权限查看的图表列表
+// @Description 查询用户在eta社区中有权限查看的图表列表
+// @Param	request	body data_manage.SetChartInfoImageReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /chart_list [get]
+func (this *EtaForumController) UserChartList() {
+	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
+	}
+	keyword := this.GetString("Keyword")
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	businessCode := utils.BusinessCode
+	userMobile := sysUser.Mobile
+	telAreaCode := sysUser.TelAreaCode
+	if businessCode == "" {
+		br.Msg = "商户号未配置"
+		return
+	}
+
+	if userMobile == "" {
+		br.Msg = "请先绑定手机号"
+		return
+	}
+	if telAreaCode == "" {
+		telAreaCode = utils.TelAreaCodeHome
+	}
+
+	resp, err, errMsg := eta_forum.GetUserChartList(businessCode, userMobile, telAreaCode, keyword, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// UserCollectChartClassifyList
+// @Title 查询社区中用户收藏的分类列表
+// @Description 查询社区中用户收藏的分类列表
+// @Param	request	body data_manage.SetChartInfoImageReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /collect/chart_classify [get]
+func (this *EtaForumController) UserCollectChartClassifyList() {
+	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
+	}
+
+	businessCode := utils.BusinessCode
+	userMobile := sysUser.Mobile
+	telAreaCode := sysUser.TelAreaCode
+	if businessCode == "" {
+		br.Msg = "商户号未配置"
+		return
+	}
+
+	if userMobile == "" {
+		br.Msg = "请先绑定手机号"
+		return
+	}
+	if telAreaCode == "" {
+		telAreaCode = utils.TelAreaCodeHome
+	}
+
+	resp, err, errMsg := eta_forum.GetUserCollectChartClassifyList(businessCode, userMobile, telAreaCode)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// UserCollectChartList
+// @Title 查询社区中用户收藏的图表列表
+// @Description 查询社区中用户收藏的图表列表
+// @Param	request	body data_manage.SetChartInfoImageReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /collect/chart [get]
+func (this *EtaForumController) UserCollectChartList() {
+	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
+	}
+
+	businessCode := utils.BusinessCode
+	userMobile := sysUser.Mobile
+	telAreaCode := sysUser.TelAreaCode
+	if businessCode == "" {
+		br.Msg = "商户号未配置"
+		return
+	}
+
+	if userMobile == "" {
+		br.Msg = "请先绑定手机号"
+		return
+	}
+	if telAreaCode == "" {
+		telAreaCode = utils.TelAreaCodeHome
+	}
+
+	collectClassifyIds := this.GetString("CollectClassifyIds")
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("Keyword")
+
+	resp, err, errMsg := eta_forum.GetUserCollectChartList(businessCode, userMobile, telAreaCode, keyword, collectClassifyIds, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// CommonChartInfoDetailFromUniqueCode
+// @Title 根据编码获取图表详情
+// @Description 根据编码获取图表详情接口
+// @Param   UniqueCode   query   int  true       "图表唯一编码,如果是管理后台访问,传固定字符串:7c69b590249049942070ae9dcd5bf6dc"
+// @Param   IsCache   query   bool  true       "是否走缓存,默认false"
+// @Success 200 {object} data_manage.ChartInfoDetailFromUniqueCodeResp
+// @router /chart/from_unique_code [get]
+func (this *EtaForumController) CommonChartInfoDetailFromUniqueCode() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,uniqueCode is empty"
+		return
+	}
+
+	//是否走缓存
+	isCache, _ := this.GetBool("IsCache")
+	resp := new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+	status := true
+	forumResp, err, _ := eta_forum.GeChartFromUniqueCode(uniqueCode, isCache)
+	if err != nil {
+		endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+		resp.EdbInfoList = endInfoList
+		resp.ChartInfo = nil
+		resp.Status = false
+
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+	chartInfo := forumResp.ChartInfo
+	if chartInfo == nil {
+		endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+		resp.EdbInfoList = endInfoList
+		resp.ChartInfo = nil
+		resp.Status = false
+
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+	resp.ChartInfo = chartInfo
+	resp.Status = status
+	resp.DataResp = forumResp.DataResp
+	resp.EdbInfoList = forumResp.EdbInfoList
+	resp.XDataList = forumResp.XDataList
+	resp.YDataList = forumResp.YDataList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 1741 - 0
controllers/material/material.go

@@ -0,0 +1,1741 @@
+package material
+
+import (
+	"archive/zip"
+	"encoding/json"
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/material"
+	materialService "eta/eta_mobile/services/material"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/http"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// MaterialController 逻辑导图
+type MaterialController struct {
+	controllers.BaseAuthController
+}
+
+// AddMaterialClassify
+// @Title 新增素材库分类
+// @Description 新增材库分类接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /classify/add [post]
+func (this *MaterialController) AddMaterialClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req material.AddMaterialClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	level := 1
+	levelPath := ""
+	if req.ParentId > 0 {
+		//查找父级分类
+		parentClassify, e := material.GetMaterialClassifyById(req.ParentId)
+		if e != nil {
+			br.Msg = "获取父级分类失败"
+			br.ErrMsg = "获取父级分类失败,Err:" + e.Error()
+			return
+		}
+		level = parentClassify.Level + 1
+		levelPath = parentClassify.LevelPath
+	}
+	var count int
+	switch this.Lang {
+	case utils.LANG_EN:
+		count, err = material.GetMaterialClassifyNameEnCount(req.ClassifyName, req.ParentId)
+	default:
+		count, err = material.GetMaterialClassifyNameCount(req.ClassifyName, req.ParentId)
+	}
+
+	if err != nil {
+		br.Msg = "判断名称是否已存在失败"
+		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "分类名称已存在,请重新输入"
+		br.IsSendEmail = false
+		return
+	}
+
+	//获取该层级下最大的排序数
+	classify := new(material.MaterialClassify)
+	maxSort, _ := material.GetMaterialClassifyMaxSort(req.ParentId)
+	classify.ParentId = req.ParentId
+	classify.ClassifyName = req.ClassifyName
+	classify.ClassifyNameEn = req.ClassifyName
+	classify.CreateTime = time.Now()
+	classify.ModifyTime = time.Now()
+	classify.SysUserId = this.SysUser.AdminId
+	classify.SysUserRealName = this.SysUser.RealName
+	classify.Level = level
+	classify.Sort = maxSort + 1
+	classifyId, e := material.AddMaterialClassify(classify)
+	if e != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + e.Error()
+		return
+	}
+	if req.ParentId > 0 {
+		levelPath = fmt.Sprintf("%s,%d", levelPath, classifyId)
+	} else {
+		levelPath = fmt.Sprintf("%d", classifyId)
+	}
+	classify.ClassifyId = int(classifyId)
+	classify.LevelPath = levelPath
+	e = classify.Update([]string{"LevelPath"})
+	if e != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+}
+
+// EditMaterialClassify
+// @Title 修改素材库分类
+// @Description 修改素材库分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /classify/edit [post]
+func (this *MaterialController) EditMaterialClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req material.EditMaterialClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 只允许修改分类名称
+	item, err := material.GetMaterialClassifyById(req.ClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在"
+			return
+		}
+		br.Msg = "保存失败"
+		br.Msg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+	var count int
+	updateStr := make([]string, 0)
+	switch this.Lang {
+	case utils.LANG_EN:
+		count, err = material.GetMaterialClassifyNameEnNotSelfCount(req.ClassifyId, req.ClassifyName, item.ParentId)
+		item.ClassifyNameEn = req.ClassifyName
+		updateStr = append(updateStr, "ClassifyNameEn")
+	default:
+		count, err = material.GetMaterialClassifyNameNotSelfCount(req.ClassifyId, req.ClassifyName, item.ParentId)
+		item.ClassifyName = req.ClassifyName
+		updateStr = append(updateStr, "ClassifyName")
+	}
+
+	if err != nil {
+		br.Msg = "判断名称是否已存在失败"
+		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "分类名称已存在,请重新输入"
+		br.IsSendEmail = false
+		return
+	}
+
+	item.ModifyTime = time.Now()
+	updateStr = append(updateStr, "ModifyTime")
+	err = item.Update(updateStr)
+	if err != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		return
+	}
+	//todo 测试更新子集的levelPath
+	/*levelPath := ""
+	oldLevelPath := item.LevelPath
+	if item.ParentId > 0 {
+		//查找父级分类
+		parentClassify, e := material.GetMaterialClassifyById(item.ParentId)
+		if e != nil {
+			br.Msg = "获取父级分类失败"
+			br.ErrMsg = "获取父级分类失败,Err:" + e.Error()
+			return
+		}
+		levelPath = fmt.Sprintf("%s,%d", parentClassify.LevelPath, item.ClassifyId)
+		tmpList, e := material.GetMaterialClassifyByLevelPath(oldLevelPath)
+		if e != nil {
+			br.Msg = "保存分类失败"
+			br.ErrMsg = "保存分类失败,Err:" + e.Error()
+			return
+		}
+		// 把原先的父级levePath,替换成最新的父级序列
+		for _, tmp := range tmpList {
+			tmp.LevelPath = strings.Replace(tmp.LevelPath, oldLevelPath, levelPath, -1)
+			tmp.ModifyTime = time.Now()
+			e = tmp.Update([]string{"LevelPath", "ModifyTime"})
+			if e != nil {
+				br.Msg = "保存分类失败"
+				br.ErrMsg = "保存分类失败,Err:" + e.Error()
+				return
+			}
+		}
+	} else {
+		// 只有更改了父级才需要更新levelPath
+	}*/
+
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// DeleteMaterialClassifyCheck
+// @Title 删除素材库检测接口
+// @Description 删除素材库检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /classify/del/check [post]
+func (this *MaterialController) DeleteMaterialClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req material.MaterialClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ClassifyId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+	//删除分类
+	// 查询当前的分类
+	classifyInfo, err := material.GetMaterialClassifyById(req.ClassifyId)
+	if err != nil {
+		br.Msg = "分类不存在"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+	classifyIds := strings.Split(classifyInfo.LevelPath, ",")
+	if len(classifyIds) > 0 {
+		//判断素材库分类下,是否含有素材库
+		count, e := material.GetMaterialInfoCountByClassifyIds(classifyIds)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有指标失败,Err:" + e.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联素材库不可删除"
+		} else {
+			if len(classifyIds) > 1 {
+				deleteStatus = 2
+				tipsMsg = "确认删除当前目录及包含的子目录吗"
+			}
+		}
+	}
+
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+
+	resp := new(material.MaterialClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// DeleteMaterialClassify
+// @Title 删除素材库分类/素材库
+// @Description 删除素材库分类/素材库接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /classify/del [post]
+func (this *MaterialController) DeleteMaterialClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req material.DeleteMaterialClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ClassifyId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 查询当前的分类
+	classifyInfo, err := material.GetMaterialClassifyById(req.ClassifyId)
+	if err != nil {
+		br.Msg = "分类不存在"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+	classifyIds := strings.Split(classifyInfo.LevelPath, ",")
+	if len(classifyIds) > 0 {
+		//判断素材库分类下,是否含有素材库
+		count, e := material.GetMaterialInfoCountByClassifyIds(classifyIds)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有指标失败,Err:" + e.Error()
+			return
+		}
+
+		if count > 0 {
+			br.Msg = "该目录下存在关联指标,不可删除"
+			br.IsSendEmail = false
+			return
+		}
+
+		err = material.DeleteMaterialClassify(classifyIds)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	//删除素材库
+	/*if req.MaterialId > 0 {
+		materialInfo, err := material.GetMaterialById(req.MaterialId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "素材库已删除,请刷新页面"
+				br.ErrMsg = "指标不存在,Err:" + err.Error()
+				return
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,获取指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+		if materialInfo == nil {
+			br.Msg = "素材库已删除,请刷新页面"
+			return
+		}
+		err = materialService.DeleteMaterial(req.MaterialId)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+	}*/
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// ClassifyMove
+// @Title 素材库分类移动接口
+// @Description 素材库分类移动接口
+// @Success 200 {object} data_manage.MoveChartClassifyReq
+// @router /classify/move [post]
+func (this *MaterialController) ClassifyMove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req material.MoveMaterialClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "分类id小于等于0"
+		return
+	}
+	//判断分类是否存在
+	materialClassifyInfo, err := material.GetMaterialClassifyById(req.ClassifyId)
+	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 materialClassifyInfo.ParentId != req.ParentClassifyId {
+		count, err := material.GetMaterialClassifyNameNotSelfCount(materialClassifyInfo.ClassifyId, materialClassifyInfo.ClassifyName, materialClassifyInfo.ParentId)
+		if err != nil {
+			br.Msg = "判断名称是否已存在失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "移动失败,分类名称已存在"
+			br.IsSendEmail = false
+			return
+		}
+	}
+	err, errMsg := materialService.MoveMaterialClassify(materialClassifyInfo, &req)
+	if err != nil {
+		br.Msg = errMsg
+		if errMsg == "" {
+			br.Msg = "移动失败"
+		}
+		br.ErrMsg = err.Error()
+	}
+
+	// todo权限校验
+	//移动的是分类
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	/*updateCol := make([]string, 0)
+	if MaterialClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+		parentChartClassifyInfo, err := material.GetMaterialClassifyById(req.ParentClassifyId)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+			return
+		}
+		MaterialClassifyInfo.ParentId = parentChartClassifyInfo.ClassifyId
+		MaterialClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+		MaterialClassifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+	} else if MaterialClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId == 0 {
+		//改为一级分类
+		MaterialClassifyInfo.ParentId = req.ParentClassifyId
+		MaterialClassifyInfo.Level = 1
+		MaterialClassifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+	}
+
+	//如果有传入 上一个兄弟节点分类id
+	if req.PrevId > 0 {
+		if req.PrevType == 1 {
+			//上一个节点是分类
+			//上一个兄弟节点
+			prevClassify, err := material.GetMaterialClassifyById(req.PrevId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+				return
+			}
+
+			//如果是移动在两个兄弟节点之间
+			if req.NextId > 0 {
+				if req.NextType == 1 {
+					//上一个节点是分类 下一个节点是分类的情况
+					//下一个兄弟节点
+					nextClassify, err := material.GetMaterialClassifyById(req.NextId)
+					if err != nil {
+						br.Msg = "移动失败"
+						br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+						return
+					}
+					//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+					if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == MaterialClassifyInfo.Sort {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 2`
+						_ = material.UpdateMaterialClassifySortByParentId(prevClassify.ParentId, prevClassify.ClassifyId, prevClassify.Sort, updateSortStr)
+						_ = material.UpdateMaterialSortByClassifyId(prevClassify.ClassifyId, prevClassify.Sort, 0, updateSortStr)
+					} else {
+						//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+						if nextClassify.Sort-prevClassify.Sort == 1 {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 1`
+							_ = material.UpdateMaterialClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+							_ = material.UpdateMaterialSortByClassifyId(prevClassify.ClassifyId, prevClassify.Sort, 0, updateSortStr)
+						}
+					}
+				} else {
+					//上一个节点是分类 下一个节点是素材库的情况
+					//下一个兄弟节点
+					nextChartInfo, err := material.GetMaterialById(req.NextId)
+					if err != nil {
+						br.Msg = "移动失败"
+						br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+						return
+					}
+					//如果上一个兄弟(分类)与下一个兄弟(素材库)的排序权重是一致的,那么需要将下一个兄弟(素材库)(以及下个兄弟(素材库)的同样排序权重)的排序权重+2,自己变成上一个兄弟(分类)的排序权重+1
+					if prevClassify.Sort == nextChartInfo.Sort || prevClassify.Sort == MaterialClassifyInfo.Sort {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 2`
+						_ = material.UpdateMaterialClassifySortByParentId(prevClassify.ParentId, prevClassify.ClassifyId, prevClassify.Sort, updateSortStr)
+						_ = material.UpdateMaterialSortByClassifyId(prevClassify.ClassifyId, prevClassify.Sort, 0, updateSortStr)
+					} else {
+						//如果下一个兄弟(素材库)的排序权重正好是上个兄弟节点(分类)的下一层,那么需要再加一层了
+						if nextChartInfo.Sort-prevClassify.Sort == 1 {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 1`
+							_ = material.UpdateMaterialClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.ClassifyId, updateSortStr)
+							_ = material.UpdateMaterialSortByClassifyId(prevClassify.ClassifyId, prevClassify.Sort, 0, updateSortStr)
+						}
+					}
+				}
+
+			}
+
+			MaterialClassifyInfo.Sort = prevClassify.Sort + 1
+			MaterialClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+
+		} else {
+			//上一个节点是素材库
+			prevMaterial, err := material.GetMaterialById(req.PrevId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+				return
+			}
+
+			//如果是移动在两个兄弟节点之间
+			if req.NextId > 0 {
+				if req.NextType == 1 {
+					//上一个节点是素材库 下一个节点是分类的情况
+					//下一个兄弟节点
+					nextClassify, err := material.GetMaterialClassifyById(req.NextId)
+					if err != nil {
+						br.Msg = "移动失败"
+						br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+						return
+					}
+					//如果上一个兄弟(素材库)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(素材库)的排序权重+1
+					if prevMaterial.Sort == nextClassify.Sort || prevMaterial.Sort == MaterialClassifyInfo.Sort {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 2`
+						_ = material.UpdateMaterialClassifySortByParentId(prevMaterial.ClassifyId, 0, prevMaterial.Sort, updateSortStr)
+						_ = material.UpdateMaterialSortByClassifyId(prevMaterial.ClassifyId, prevMaterial.Sort, prevMaterial.MaterialId, updateSortStr)
+					} else {
+						//如果下一个兄弟(分类)的排序权重正好是上个兄弟(素材库)节点的下一层,那么需要再加一层了
+						if nextClassify.Sort-prevMaterial.Sort == 1 {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 1`
+							_ = material.UpdateMaterialClassifySortByParentId(prevMaterial.ClassifyId, 0, prevMaterial.Sort, updateSortStr)
+							_ = material.UpdateMaterialSortByClassifyId(prevMaterial.ClassifyId, prevMaterial.Sort, prevMaterial.MaterialId, updateSortStr)
+						}
+					}
+				} else {
+					//上一个节点是素材库 下一个节点是素材库的情况
+					//下一个兄弟节点
+					nextChartInfo, err := material.GetMaterialById(req.NextId)
+					if err != nil {
+						br.Msg = "移动失败"
+						br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+						return
+					}
+					//如果上一个兄弟(素材库)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(素材库)的排序权重+1
+					if prevMaterial.Sort == nextChartInfo.Sort || prevMaterial.Sort == MaterialClassifyInfo.Sort {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 2`
+						_ = material.UpdateMaterialClassifySortByParentId(prevMaterial.ClassifyId, 0, prevMaterial.Sort, updateSortStr)
+						_ = material.UpdateMaterialSortByClassifyId(prevMaterial.ClassifyId, prevMaterial.Sort, prevMaterial.MaterialId, updateSortStr)
+					} else {
+						//如果下一个兄弟(分类)的排序权重正好是上个兄弟(素材库)节点的下一层,那么需要再加一层了
+						if nextChartInfo.Sort-prevMaterial.Sort == 1 {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 1`
+							_ = material.UpdateMaterialClassifySortByParentId(prevMaterial.ClassifyId, 0, prevMaterial.Sort, updateSortStr)
+							_ = material.UpdateMaterialSortByClassifyId(prevMaterial.ClassifyId, prevMaterial.Sort, prevMaterial.MaterialId, updateSortStr)
+						}
+					}
+				}
+
+			}
+			MaterialClassifyInfo.Sort = prevMaterial.Sort + 1
+			MaterialClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+
+		}
+
+	} else {
+		firstClassify, err := material.GetFirstMaterialClassifyByParentId(MaterialClassifyInfo.ParentId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstClassify != nil && firstClassify.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = material.UpdateMaterialClassifySortByParentId(firstClassify.ParentId, firstClassify.ClassifyId-1, 0, updateSortStr)
+		}
+
+		MaterialClassifyInfo.Sort = 0 //那就是排在第一位
+		MaterialClassifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = MaterialClassifyInfo.Update(updateCol)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "修改失败,Err:" + err.Error()
+			return
+		}
+		// todo 记录整个层级的分类ID
+	}*/
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+// List
+// @Title 素材列表接口
+// @Description 素材列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   int  true       "分类id"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /list [get]
+func (this *MaterialController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	classifyId, _ := this.GetInt("ClassifyId")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("Keyword")
+
+	var total int
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	classifyIds := make([]string, 0)
+	childClassifyMap := make(map[int]*material.MaterialClassify)
+	if classifyId > 0 {
+		classifyIds = append(classifyIds, strconv.Itoa(classifyId))
+		// 查询当前的分类
+		classifyInfo, err := material.GetMaterialClassifyById(classifyId)
+		if err != nil {
+			br.Msg = "分类不存在"
+			br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+			return
+		}
+		// 获取所有子分类
+		childList, e := material.GetMaterialClassifyByLevelPath(classifyInfo.LevelPath)
+		if e != nil {
+			err = fmt.Errorf("保存分类失败,Err:" + e.Error())
+			return
+		}
+		// 把原先的父级levePath,替换成最新的父级序列
+		classifyIdMap := make(map[string]struct{})
+		for _, tmp := range childList {
+			childClassifyMap[tmp.ClassifyId] = tmp
+			//获取字符串前缀的位置
+			after, _ := strings.CutPrefix(tmp.LevelPath, classifyInfo.LevelPath)
+			fmt.Println("after", after)
+			// 拼接字符串
+			if after != "" {
+				ids := strings.Split(after, ",")
+				for _, v := range ids {
+					if _, ok := classifyIdMap[v]; !ok {
+						classifyIds = append(classifyIds, v)
+						classifyIdMap[v] = struct{}{}
+					}
+				}
+			}
+		}
+	}
+
+	if len(classifyIds) > 0 {
+		condition += " AND classify_id IN(" + utils.GetOrmInReplace(len(classifyIds)) + ") "
+		pars = append(pars, classifyIds)
+	}
+
+	if keyword != "" {
+		switch this.Lang {
+		case utils.LANG_EN:
+			condition += ` AND  ( material_name_en LIKE '%` + keyword + `%' )`
+		default:
+			condition += ` AND  ( material_name LIKE '%` + keyword + `%' )`
+		}
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		condition += ` AND sys_user_id = ? `
+		pars = append(pars, sysUser.AdminId)
+	}
+
+	//获取图表信息
+	list, err := material.GetMaterialListPageByCondition(condition, pars, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取素材库信息失败"
+		br.ErrMsg = "获取素材库信息失败,Err:" + err.Error()
+		return
+	}
+
+	resp := new(material.MaterialListResp)
+	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
+		items := make([]*material.MaterialListItems, 0)
+		resp.Paging = page
+		resp.List = items
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	dataCount, err := material.GetMaterialListCountByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, dataCount)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// SaveAsMaterial
+// @Title 将图表等封面上传至素材库
+// @Description 将图表等封面上传至素材库
+// @Param	request	body material.AddAndEditSandbox true "type json string"
+// @Success 200 {object} material.Material
+// @router /saveAs [post]
+func (this *MaterialController) SaveAsMaterial() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.SaveAsMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.MaterialName = strings.Trim(req.MaterialName, " ")
+	if req.MaterialName == "" {
+		br.Msg = "缺少素材库名称"
+		return
+	}
+	var exist *material.Material
+	switch this.Lang {
+	case utils.LANG_EN:
+		exist, err = material.GetMaterialByNameEn(req.MaterialName)
+	default:
+		// 判断名称是否重复
+		exist, err = material.GetMaterialByName(req.MaterialName)
+	}
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	if err == nil && exist.MaterialId > 0 {
+		br.Msg = "图片名称已存在"
+		return
+	}
+	err, errMsg := materialService.AddToMaterial(req, sysUser.AdminId, sysUser.RealName)
+	if err != nil {
+		br.Msg = "保存失败!"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	msg := "保存成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// MyChartSaveAsMaterial
+// @Title 将我的图表等封面上传至素材库
+// @Description 将图表等封面上传至素材库
+// @Param	request	body material.AddAndEditSandbox true "type json string"
+// @Success 200 {object} material.Material
+// @router /my_chart/saveAs [post]
+func (this *MaterialController) MyChartSaveAsMaterial() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.MyChartSaveAsMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	materialNames := make([]string, 0)
+	namesMap := make(map[string]struct{})
+	for _, v := range req.MaterialList {
+		if v.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+		v.MaterialName = strings.Trim(v.MaterialName, " ")
+		if v.MaterialName == "" {
+			br.Msg = "请填写图片名称"
+			return
+		}
+		if v.ChartInfoId <= 0 {
+			br.Msg = "请选择图表"
+			return
+		}
+		if v.MyChartId <= 0 {
+			br.Msg = "请选择我的图表"
+			return
+		}
+		if _, ok := namesMap[v.MaterialName]; ok {
+			br.Msg = "图片名称不能重复"
+			return
+		}
+		namesMap[v.MaterialName] = struct{}{}
+		materialNames = append(materialNames, v.MaterialName)
+	}
+	if len(materialNames) == 0 {
+		br.Msg = "请填写图片名称"
+		return
+	}
+	existList := make([]*material.Material, 0)
+	switch this.Lang {
+	case utils.LANG_EN:
+		existList, err = material.GetMaterialByNameEns(materialNames)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if len(existList) > 0 {
+			msg := "图片名称:"
+			for _, v := range existList {
+				msg += v.MaterialNameEn + " "
+			}
+			br.Msg = fmt.Sprintf("%s 已存在", msg)
+			return
+		}
+	default:
+		// 判断文件名是否已存在
+		existList, err = material.GetMaterialByNames(materialNames)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if len(existList) > 0 {
+			msg := "图片名称:"
+			for _, v := range existList {
+				msg += v.MaterialName + " "
+			}
+			br.Msg = fmt.Sprintf("%s 已存在", msg)
+			return
+		}
+	}
+	if len(req.MaterialList) > 30 {
+		br.Msg = "最多支持选择30个图表"
+		return
+	}
+
+	err, errMsg := materialService.MyChartAddToMaterial(req, sysUser.AdminId, sysUser.RealName)
+	if err != nil {
+		br.Msg = "保存失败!"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	msg := "保存成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// Delete
+// @Title 删除素材库
+// @Description 删除素材库接口
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /del [post]
+func (this *MaterialController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.DeleteMaterial
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.MaterialId <= 0 {
+		br.Msg = "缺少素材库编号"
+		return
+	}
+
+	//删除素材库
+	err = material.DeleteByMaterialIds([]int{req.MaterialId})
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "操作失败,Err:" + err.Error()
+		return
+	}
+
+	msg := "删除成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// BatchDelete
+// @Title 批量删除素材库
+// @Description 删除素材库接口
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /batch/del [post]
+func (this *MaterialController) BatchDelete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.BatchDeleteMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	deleteMaterialIds := make([]int, 0)
+	if req.IsSelectAll {
+		classifyId := req.ClassifyId
+		keyword := req.Keyword
+		isShowMe := req.IsShowMe
+		//获取图表信息
+		list, e, msg := materialService.GetBatchSelectedMaterialList(classifyId, keyword, isShowMe, sysUser, this.Lang)
+		if e != nil {
+			br.Msg = "获取素材库信息失败"
+			if msg != "" {
+				br.Msg = msg
+			}
+			br.ErrMsg = "获取素材库信息失败,Err:" + e.Error()
+			return
+		}
+		notSelectIds := make(map[int]struct{})
+		if len(req.MaterialIds) >= 0 {
+			for _, v := range req.MaterialIds {
+				notSelectIds[v] = struct{}{}
+			}
+		}
+		for _, v := range list {
+			if _, ok := notSelectIds[v.MaterialId]; !ok {
+				deleteMaterialIds = append(deleteMaterialIds, v.MaterialId)
+			}
+		}
+	} else {
+		deleteMaterialIds = req.MaterialIds
+	}
+
+	if len(deleteMaterialIds) <= 0 {
+		br.Msg = "请选择删除的素材"
+		return
+	}
+
+	//删除素材库
+	err = material.DeleteByMaterialIds(deleteMaterialIds)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "删除成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// MaterialClassifyList
+// @Title 获取所有素材库分类接口-不包含素材库
+// @Description 获取所有素材库分类接口-不包含素材库
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /classify/list [get]
+func (this *MaterialController) MaterialClassifyList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := new(material.MaterialClassifyListResp)
+
+	rootList, err := material.GetMaterialClassifyByParentId(0)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	classifyAll, err := material.GetMaterialClassifyAll()
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	nodeAll := make([]*material.MaterialClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		materialService.MaterialClassifyItemsMakeTree(this.SysUser, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// BatchAdd
+// @Title 批量新增素材
+// @Description 新增/编辑保存素材库接口
+// @Param	request	body material.AddAndEditSandbox true "type json string"
+// @Success 200 {object} material.Material
+// @router /batch/add [post]
+func (this *MaterialController) BatchAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.BatchAddMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	classifyId := req.ClassifyId
+	if classifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	materialNames := make([]string, 0)
+	namesMap := make(map[string]struct{})
+	for _, v := range req.MaterialList {
+		if v.MaterialName == "" {
+			br.Msg = "请填写图片名称"
+			return
+		}
+		if v.ImgUrl == "" {
+			br.Msg = "请上传图片"
+			return
+		}
+		if _, ok := namesMap[v.MaterialName]; ok {
+			br.Msg = "图片名称不能重复"
+			return
+		}
+		namesMap[v.MaterialName] = struct{}{}
+		materialNames = append(materialNames, v.MaterialName)
+	}
+	if len(materialNames) == 0 {
+		br.Msg = "请填写图片名称"
+		return
+	}
+	existList := make([]*material.Material, 0)
+	switch this.Lang {
+	case utils.LANG_EN:
+		existList, err = material.GetMaterialByNameEns(materialNames)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if len(existList) > 0 {
+			msg := "图片名称:"
+			for _, v := range existList {
+				msg += v.MaterialNameEn + " "
+			}
+			br.Msg = fmt.Sprintf("%s 已存在", msg)
+			return
+		}
+	default:
+		// 判断文件名是否已存在
+		existList, err = material.GetMaterialByNames(materialNames)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if len(existList) > 0 {
+			msg := "图片名称:"
+			for _, v := range existList {
+				msg += v.MaterialName + " "
+			}
+			br.Msg = fmt.Sprintf("%s 已存在", msg)
+			return
+		}
+	}
+	err = materialService.BatchAddMaterial(req.MaterialList, classifyId, sysUser.AdminId, sysUser.AdminName)
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+	msg := "添加成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// BatchChangeClassify
+// @Title 批量更换分类
+// @Description 批量更换分类
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /batch/changeClassify [post]
+func (this *MaterialController) BatchChangeClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.BatchChangeClassifyMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.NewClassifyId <= 0 {
+		br.Msg = "请选择新的分类"
+		return
+	}
+	updateMaterialIds := make([]int, 0)
+	if req.IsSelectAll {
+		classifyId := req.ClassifyId
+		keyword := req.Keyword
+		isShowMe := req.IsShowMe
+		//获取图表信息
+		list, e, msg := materialService.GetBatchSelectedMaterialList(classifyId, keyword, isShowMe, sysUser, this.Lang)
+		if e != nil {
+			br.Msg = "获取素材库信息失败"
+			if msg != "" {
+				br.Msg = msg
+			}
+			br.ErrMsg = "获取素材库信息失败,Err:" + e.Error()
+			return
+		}
+		notSelectIds := make(map[int]struct{})
+		if len(req.MaterialIds) >= 0 {
+			for _, v := range req.MaterialIds {
+				notSelectIds[v] = struct{}{}
+			}
+		}
+		for _, v := range list {
+			if _, ok := notSelectIds[v.MaterialId]; !ok {
+				updateMaterialIds = append(updateMaterialIds, v.MaterialId)
+			}
+		}
+	} else {
+		updateMaterialIds = req.MaterialIds
+	}
+
+	if len(updateMaterialIds) <= 0 {
+		br.Msg = "请选择变更的素材"
+		return
+	}
+
+	// 判断新分类是否存在
+	_, err = material.GetMaterialClassifyById(req.NewClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在"
+			return
+		}
+		br.Msg = "获取分类信息失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+	}
+	//更换分类素材库
+	err = material.UpdateClassifyByMaterialIds(updateMaterialIds, req.NewClassifyId, time.Now())
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// ChangeClassify
+// @Title 更换分类
+// @Description 批量更换分类
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /changeClassify [post]
+func (this *MaterialController) ChangeClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.ChangeClassifyMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.MaterialId <= 0 {
+		br.Msg = "缺少素材库编号"
+		return
+	}
+	if req.NewClassifyId <= 0 {
+		br.Msg = "请选择新的分类"
+		return
+	}
+
+	// 判断素材是否存在
+	info, err := material.GetMaterialById(req.MaterialId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "素材不存在"
+			return
+		}
+		br.Msg = "获取素材库信息失败"
+		br.ErrMsg = "获取素材库信息失败,Err:" + err.Error()
+		return
+	}
+
+	if req.NewClassifyId == info.ClassifyId {
+		br.Msg = "请选择不同的分类"
+		return
+	}
+
+	// 判断新分类是否存在
+	_, err = material.GetMaterialClassifyById(req.NewClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在"
+			return
+		}
+		br.Msg = "获取分类信息失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+	}
+	info.ClassifyId = req.NewClassifyId
+	info.ModifyTime = time.Now()
+
+	//更换分类素材库
+	err = info.Update([]string{"ClassifyId", "ModifyTime"})
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// Rename
+// @Title 素材重命名
+// @Description 素材重命名
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /rename [post]
+func (this *MaterialController) Rename() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req material.RenameMaterialReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.MaterialId <= 0 {
+		br.Msg = "缺少素材库编号"
+		return
+	}
+	req.MaterialName = strings.Trim(req.MaterialName, " ")
+	if req.MaterialName == "" {
+		br.Msg = "缺少素材库名称"
+		return
+	}
+	// 判断素材是否存在
+	info, err := material.GetMaterialById(req.MaterialId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "素材不存在"
+			return
+		}
+		br.Msg = "获取素材库信息失败"
+		br.ErrMsg = "获取素材库信息失败,Err:" + err.Error()
+		return
+	}
+	var exist *material.Material
+	updateStr := make([]string, 0)
+	switch this.Lang {
+	case utils.LANG_EN:
+		// 判断名称是否重复
+		if info.MaterialNameEn == req.MaterialName {
+			br.Msg = "名称未修改"
+			return
+		}
+		exist, err = material.GetMaterialByNameEn(req.MaterialName)
+		info.MaterialNameEn = req.MaterialName
+		updateStr = append(updateStr, "MaterialNameEn")
+	default:
+		// 判断名称是否重复
+		if info.MaterialName == req.MaterialName {
+			br.Msg = "名称未修改"
+			return
+		}
+		exist, err = material.GetMaterialByName(req.MaterialName)
+		info.MaterialName = req.MaterialName
+		updateStr = append(updateStr, "MaterialName")
+	}
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	if err == nil && exist.MaterialId > 0 {
+		br.Msg = "图片名称已存在"
+		return
+	}
+	info.ModifyTime = time.Now()
+	updateStr = append(updateStr, "ModifyTime")
+	//更换分类素材库
+	err = info.Update(updateStr)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// BatchDownload
+// @Title 批量下载素材
+// @Description 批量下载素材
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /batch/download [get]
+func (this *MaterialController) BatchDownload() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	//var req material.BatchDeleteMaterialReq
+	materialIdStr := this.GetString("MaterialIds")
+	isSelectAll, _ := this.GetBool("IsSelectAll")
+	classifyId, _ := this.GetInt("ClassifyId")
+	keyword := this.GetString("Keyword")
+	isShowMe, _ := this.GetBool("IsShowMe")
+
+	downMaterialList := make([]*material.MaterialListItems, 0)
+	materialIds := strings.Split(materialIdStr, ",")
+	var err error
+	if isSelectAll {
+		//获取图表信息
+		list, e, msg := materialService.GetBatchSelectedMaterialList(classifyId, keyword, isShowMe, sysUser, this.Lang)
+		if e != nil {
+			br.Msg = "获取素材库信息失败"
+			if msg != "" {
+				br.Msg = msg
+			}
+			br.ErrMsg = "获取素材库信息失败,Err:" + e.Error()
+			return
+		}
+		notSelectIds := make(map[int]struct{})
+		if len(materialIds) >= 0 {
+			//转成数组
+			for _, v := range materialIds {
+				id, _ := strconv.Atoi(v)
+				notSelectIds[id] = struct{}{}
+			}
+		}
+		for _, v := range list {
+			if _, ok := notSelectIds[v.MaterialId]; !ok {
+				downMaterialList = append(downMaterialList, v)
+			}
+		}
+	} else {
+		if len(materialIds) > 0 {
+			// 批量查询指标数据
+			materialIdsInt := make([]int, 0)
+			for _, v := range materialIds {
+				id, _ := strconv.Atoi(v)
+				materialIdsInt = append(materialIdsInt, id)
+			}
+			downMaterialList, err = material.GetMaterialByIds(materialIdsInt)
+			if err != nil {
+				br.Msg = "获取素材库信息失败"
+				br.ErrMsg = "获取素材库信息失败,Err:" + err.Error()
+				return
+			}
+		}
+	}
+
+	if len(downMaterialList) <= 0 {
+		br.Msg = "请选择要下载的素材"
+		return
+	}
+	// 创建zip
+	zipName := time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".zip"
+	savePath := zipName
+	zipFile, err := os.Create(zipName)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "生成压缩文件失败, Err: " + err.Error()
+		return
+	}
+	zipWriter := zip.NewWriter(zipFile)
+	// 生成zip过程中报错关闭
+	defer func() {
+		if err != nil {
+			_ = zipWriter.Close()
+			_ = zipFile.Close()
+		}
+		_ = os.Remove(savePath)
+	}()
+
+	// 获取资源, 写入zip
+	zipFileName := ""
+	for i := range downMaterialList {
+		if downMaterialList[i].MaterialName == "" || downMaterialList[i].ImgUrl == "" {
+			continue
+		}
+		fmt.Printf("开始压缩第%d个文件\n", i+1)
+		dotIndex := strings.LastIndex(downMaterialList[i].ImgUrl, ".")
+
+		// 如果找不到点,或者点是文件名的第一个字符(不合法情况),则返回空字符串
+		if dotIndex == -1 || dotIndex == 0 {
+			continue
+		}
+
+		fileExt := downMaterialList[i].ImgUrl[dotIndex+1:]
+		ioWriter, err := zipWriter.Create(fmt.Sprintf("%s.%s", downMaterialList[i].MaterialName, fileExt))
+		if err != nil {
+			if os.IsPermission(err) {
+				br.Msg = "操作失败"
+				br.ErrMsg = "打包权限不足, Err: " + err.Error()
+				return
+			}
+			br.Msg = "操作失败"
+			br.ErrMsg = "压缩出错, Err: " + err.Error()
+			return
+		}
+
+		var content []byte
+		content, err = http.Get(downMaterialList[i].ImgUrl)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "资源获取失败, Err: " + err.Error()
+			return
+		}
+		_, err = ioWriter.Write(content)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "压缩文件写入失败, Err: " + err.Error()
+			return
+		}
+		if zipFileName == "" {
+			zipFileName = downMaterialList[i].MaterialName
+		}
+		fmt.Printf("第%d个文件写入成功\n", i+1)
+	}
+	// 生成zip后关闭,否则下载文件会损坏
+	_ = zipWriter.Close()
+	_ = zipFile.Close()
+
+	this.Ctx.Output.Download(savePath, fmt.Sprintf("%s.zip", zipFileName))
+
+	msg := "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// Download
+// @Title 下载素材
+// @Description 下载素材
+// @Param	request	body material.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /download [get]
+func (this *MaterialController) Download() {
+	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
+	}
+	materialId, _ := this.GetInt("MaterialId")
+	if materialId <= 0 {
+		br.Msg = "缺少素材库编号"
+		return
+	}
+	// 判断素材是否存在
+	info, err := material.GetMaterialById(materialId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "素材不存在"
+			return
+		}
+		br.Msg = "获取素材库信息失败"
+		br.ErrMsg = "获取素材库信息失败,Err:" + err.Error()
+		return
+	}
+	if info.ImgUrl == "" {
+		br.Msg = "素材地址为空"
+		return
+	}
+
+	fileName := info.MaterialName
+	// 查找文件名中最后一个点的位置
+	dotIndex := strings.LastIndex(info.ImgUrl, ".")
+
+	// 如果找不到点,或者点是文件名的第一个字符(不合法情况),则返回空字符串
+	if dotIndex == -1 || dotIndex == 0 {
+		br.Msg = "素材地址错误"
+		return
+	}
+
+	fileName += "." + info.ImgUrl[dotIndex+1:] // 添加扩展名到文件名
+	// 获取路径中的文件名
+	urlFileName := path.Base(info.ImgUrl)
+	uploadDir := utils.STATIC_DIR + "hongze/" + time.Now().Format("20060102")
+	if e := os.MkdirAll(uploadDir, utils.DIR_MOD); e != nil {
+		br.Msg = "存储目录创建失败"
+		br.ErrMsg = "存储目录创建失败, Err:" + e.Error()
+		return
+	}
+	var content []byte
+	content, err = http.Get(info.ImgUrl)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "资源获取失败, Err: " + err.Error()
+		return
+	}
+	filePath := uploadDir + "/" + urlFileName
+	ioWriter, err := os.Create(filePath)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "文件创建失败, Err: " + err.Error()
+		return
+	}
+	n, err := ioWriter.Write(content)
+	fmt.Println("n", n)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "压缩文件写入失败, Err: " + err.Error()
+		return
+	}
+	this.Ctx.Output.Download(filePath, fileName)
+
+	defer func() {
+		_ = os.Remove(filePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 438 - 493
controllers/report.go


+ 1 - 1
controllers/report_approve/report_approve.go

@@ -43,7 +43,7 @@ func (this *ReportApproveController) CheckApproveOpen() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()

+ 61 - 38
controllers/report_approve/report_approve_flow.go

@@ -56,6 +56,8 @@ func (this *ReportApproveFlowController) List() {
 
 	var cond, orderRule string
 	var pars []interface{}
+	cond += fmt.Sprintf(` AND %s = ? `, report_approve.ReportApproveFlowCols.Enabled)
+	pars = append(pars, 1)
 	// 筛选项
 	{
 		keyword := strings.TrimSpace(params.Keyword)
@@ -68,6 +70,10 @@ func (this *ReportApproveFlowController) List() {
 			cond += fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifySecondId)
 			pars = append(pars, params.ReportType, params.ClassifySecondId)
 		}
+		if params.ClassifyThirdId > 0 {
+			cond += fmt.Sprintf(` AND %s = ? `, report_approve.ReportApproveFlowCols.ClassifyThirdId)
+			pars = append(pars, params.ReportType, params.ClassifyThirdId)
+		}
 		if params.SortRule > 0 {
 			orderMap := map[int]string{1: "ASC", 2: "DESC"}
 			orderRule = fmt.Sprintf("%s %s", report_approve.ReportApproveFlowCols.CreateTime, orderMap[params.SortRule])
@@ -137,7 +143,14 @@ func (this *ReportApproveFlowController) List() {
 		if v.ReportType == report_approve.FlowReportTypeEnglish {
 			t.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], enClassifyIdName[enRootIdMap[v.ClassifySecondId]], enClassifyIdName[v.ClassifyFirstId], enClassifyIdName[v.ClassifySecondId])
 		} else {
-			t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+			reportClassify := fmt.Sprintf("%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId])
+			if v.ClassifySecondId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifySecondId])
+			}
+			if v.ClassifyThirdId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifyThirdId])
+			}
+			t.ReportClassify = reportClassify
 		}
 		resp.List = append(resp.List, t)
 	}
@@ -193,7 +206,7 @@ func (this *ReportApproveFlowController) Add() {
 		br.ErrMsg = fmt.Sprintf("审批流报告类型有误, ReportType: %d", req.ReportType)
 		return
 	}
-	if req.ClassifyFirstId <= 0 || req.ClassifySecondId <= 0 {
+	if req.ClassifyFirstId <= 0 && req.ClassifySecondId <= 0 && req.ClassifyThirdId <= 0 {
 		br.Msg = "请选择报告分类"
 		return
 	}
@@ -221,9 +234,9 @@ func (this *ReportApproveFlowController) Add() {
 	// 审批流是否已存在
 	{
 		flowOb := new(report_approve.ReportApproveFlow)
-		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId)
 		existPars := make([]interface{}, 0)
-		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId)
 		exist, e := flowOb.GetItemByCondition(existCond, existPars, "")
 		if e != nil && e.Error() != utils.ErrNoRow() {
 			br.Msg = "获取失败"
@@ -241,7 +254,9 @@ func (this *ReportApproveFlowController) Add() {
 	flowItem.ReportType = req.ReportType
 	flowItem.ClassifyFirstId = req.ClassifyFirstId
 	flowItem.ClassifySecondId = req.ClassifySecondId
+	flowItem.ClassifyThirdId = req.ClassifyThirdId
 	flowItem.CurrVersion = 1
+	flowItem.Enabled = 1
 	flowItem.CreateTime = time.Now().Local()
 	flowItem.ModifyTime = time.Now().Local()
 
@@ -273,7 +288,7 @@ func (this *ReportApproveFlowController) Add() {
 
 	// 更新审批对应的报告状态:未发布->待提交
 	go func() {
-		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
 	}()
 
 	br.Data = detail
@@ -330,7 +345,7 @@ func (this *ReportApproveFlowController) Edit() {
 		br.ErrMsg = fmt.Sprintf("审批流报告类型有误, ReportType: %d", req.ReportType)
 		return
 	}
-	if req.ClassifyFirstId <= 0 || req.ClassifySecondId <= 0 {
+	if req.ClassifyFirstId <= 0 && req.ClassifySecondId <= 0 && req.ClassifyThirdId <= 0 {
 		br.Msg = "请选择报告分类"
 		return
 	}
@@ -369,9 +384,9 @@ func (this *ReportApproveFlowController) Edit() {
 
 	// 审批流是否已存在
 	{
-		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s <> ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ReportApproveFlowId)
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?  AND %s = ? AND %s <> ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId, report_approve.ReportApproveFlowCols.ReportApproveFlowId)
 		existPars := make([]interface{}, 0)
-		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ReportApproveFlowId)
+		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId, req.ReportApproveFlowId)
 		exist, e := flowOb.GetItemByCondition(existCond, existPars, "")
 		if e != nil && e.Error() != utils.ErrNoRow() {
 			br.Msg = "操作失败"
@@ -403,15 +418,15 @@ func (this *ReportApproveFlowController) Edit() {
 	}
 
 	// 变更了报告分类时, 判断是否允许变更
-	if req.ReportType != flowItem.ReportType || req.ClassifyFirstId != flowItem.ClassifyFirstId || req.ClassifySecondId != flowItem.ClassifySecondId {
-		checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+	if req.ReportType != flowItem.ReportType || req.ClassifyFirstId != flowItem.ClassifyFirstId || req.ClassifySecondId != flowItem.ClassifySecondId || req.ClassifyThirdId != flowItem.ClassifyThirdId {
+		checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId)
 		if e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "校验审批流是否可变更失败, Err: " + e.Error()
 			return
 		}
 		if !checkOk {
-			br.Msg = "当前有未走完流程的报告, 请走完流程后再做变更!"
+			br.Msg = "当前有未走完流程的报告, 请走完流程后再做变更"
 			return
 		}
 	}
@@ -420,6 +435,7 @@ func (this *ReportApproveFlowController) Edit() {
 	flowItem.ReportType = req.ReportType
 	flowItem.ClassifyFirstId = req.ClassifyFirstId
 	flowItem.ClassifySecondId = req.ClassifySecondId
+	flowItem.ClassifyThirdId = req.ClassifyThirdId
 	flowItem.CurrVersion += 1
 	flowItem.ModifyTime = time.Now().Local()
 
@@ -566,7 +582,7 @@ func (this *ReportApproveFlowController) Remove() {
 	}
 
 	// 校验是否允许删除
-	checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+	checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验审批流是否可变更失败, Err: " + e.Error()
@@ -586,7 +602,7 @@ func (this *ReportApproveFlowController) Remove() {
 
 	// 更新审批对应的报告状态:待提交->未发布
 	go func() {
-		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
 	}()
 
 	br.Ret = 200
@@ -645,7 +661,7 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 	}
 	hasFlowMap := make(map[string]bool)
 	for _, v := range flows {
-		k := fmt.Sprintf("%d-%d-%d", v.ReportType, v.ClassifyFirstId, v.ClassifySecondId)
+		k := fmt.Sprintf("%d-%d-%d-%d", v.ReportType, v.ClassifyFirstId, v.ClassifySecondId, v.ClassifyThirdId)
 		if k == flowKey {
 			// 当前审批流对应的分类标记为可选状态
 			continue
@@ -653,7 +669,7 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		hasFlowMap[k] = true
 	}
 
-	resp, cnTree, smartTree, enTree := make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0)
+	resp, cnTree, enTree := make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0)
 
 	var cnErr, enErr error
 	wg := sync.WaitGroup{}
@@ -672,17 +688,16 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		}
 		cnTree = services.GetReportClassifyTreeRecursive(classify, 0)
 		for _, v := range cnTree {
-			for _, v2 := range v.Children {
-				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId)
-				v2.HasFlow = hasFlowMap[k]
-			}
-		}
+			k1 := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, 0, 0)
+			v.HasFlow = hasFlowMap[k1]
 
-		smartTree = services.GetReportClassifyTreeRecursive(classify, 0)
-		for _, v := range smartTree {
 			for _, v2 := range v.Children {
-				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeSmart, v.ClassifyId, v2.ClassifyId)
-				v2.HasFlow = hasFlowMap[k]
+				k2 := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId, 0)
+				v2.HasFlow = hasFlowMap[k2]
+				for _, v3 := range v2.Children {
+					k3 := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId, v3.ClassifyId)
+					v3.HasFlow = hasFlowMap[k3]
+				}
 			}
 		}
 	}()
@@ -701,7 +716,7 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		enTree = services.GetReportClassifyTreeRecursive(classify, 0)
 		for _, v := range enTree {
 			for _, v2 := range v.Children {
-				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeEnglish, v.ClassifyId, v2.ClassifyId)
+				k := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeEnglish, v.ClassifyId, v2.ClassifyId, 0)
 				v2.HasFlow = hasFlowMap[k]
 			}
 		}
@@ -720,19 +735,27 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		return
 	}
 
-	resp = append(resp, &report_approve.ReportClassifyTreeItem{
-		ClassifyId:   report_approve.FlowReportTypeChinese,
-		ClassifyName: "研报列表",
-		Children:     cnTree,
-	}, &report_approve.ReportClassifyTreeItem{
-		ClassifyId:   report_approve.FlowReportTypeEnglish,
-		ClassifyName: "英文研报",
-		Children:     enTree,
-	}, &report_approve.ReportClassifyTreeItem{
-		ClassifyId:   report_approve.FlowReportTypeSmart,
-		ClassifyName: "智能研报",
-		Children:     smartTree,
-	})
+	if this.Lang == utils.EnLangVersion {
+		resp = append(resp, &report_approve.ReportClassifyTreeItem{
+			ClassifyId:   report_approve.FlowReportTypeChinese,
+			ClassifyName: "Report list",
+			Children:     cnTree,
+		}, &report_approve.ReportClassifyTreeItem{
+			ClassifyId:   report_approve.FlowReportTypeEnglish,
+			ClassifyName: "English Report",
+			Children:     enTree,
+		})
+	} else {
+		resp = append(resp, &report_approve.ReportClassifyTreeItem{
+			ClassifyId:   report_approve.FlowReportTypeChinese,
+			ClassifyName: "研报",
+			Children:     cnTree,
+		}, &report_approve.ReportClassifyTreeItem{
+			ClassifyId:   report_approve.FlowReportTypeEnglish,
+			ClassifyName: "英文研报",
+			Children:     enTree,
+		})
+	}
 
 	br.Data = resp
 	br.Ret = 200

+ 1721 - 0
controllers/report_chapter.go

@@ -0,0 +1,1721 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/report"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/services"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"html"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AddChapter
+// @Title 新增晨周报章节内容
+// @Description 新增晨周报章节内容
+// @Param	request	body models.AddReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/add [post]
+func (this *ReportController) AddChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.AddReportChapterReq
+	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 = "报告ID有误"
+		return
+	}
+
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == `` {
+		br.Msg = "章节名称不能为空"
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(req.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许新增"
+		br.ErrMsg = "该报告已发布,不允许新增"
+		return
+	}
+
+	//newContent := req.Content
+	//// 更新章节及指标
+	//contentSub := ""
+	//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)
+	//	if e != nil {
+	//		br.Msg = "内容去除前后空格失败"
+	//		br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+	//		return
+	//	}
+	//	req.Content = contentClean
+	//
+	//	contentSub, err = services.GetReportContentSub(req.Content)
+	//	if err != nil {
+	//		br.Msg = "内容分段解析失败"
+	//		br.ErrMsg = "编辑报告章节-解析 ContentSub 失败, Err: " + err.Error()
+	//		return
+	//	}
+	//}
+	//if req.Content == "" {
+	//	req.Content = newContent
+	//}
+
+	// 最小单元的分类
+	var minClassifyId int
+	var minClassifyName string
+	if reportInfo.ClassifyIdThird > 0 {
+		minClassifyId = reportInfo.ClassifyIdThird
+		minClassifyName = reportInfo.ClassifyNameThird
+	} else if reportInfo.ClassifyIdSecond > 0 {
+		minClassifyId = reportInfo.ClassifyIdSecond
+		minClassifyName = reportInfo.ClassifyNameSecond
+	} else {
+		minClassifyId = reportInfo.ClassifyIdFirst
+		minClassifyName = reportInfo.ClassifyNameFirst
+	}
+
+	// 判断名称是否重复
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND report_id = ? AND title=? "
+		pars = append(pars, req.ReportId, req.Title)
+		count, err := models.GetCountReportChapterByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "新增失败"
+			br.ErrMsg = "判断章节名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "章节名称不允许重复"
+			br.ErrMsg = "章节名称不允许重复"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	reportChapterInfo := new(models.ReportChapter)
+	reportChapterInfo.ReportId = reportInfo.Id
+	reportChapterInfo.ClassifyIdFirst = minClassifyId
+	reportChapterInfo.ClassifyNameFirst = minClassifyName
+
+	reportChapterInfo.Title = req.Title
+	reportChapterInfo.AddType = 1
+	reportChapterInfo.PublishState = 1
+	//reportChapterInfo.Author = req.Author
+	//reportChapterInfo.Content = html.EscapeString(req.Content)
+	//reportChapterInfo.ContentSub = html.EscapeString(contentSub)
+	reportChapterInfo.IsEdit = 1
+	//reportChapterInfo.CreateTime = req.CreateTime
+	reportChapterInfo.CreateTime = reportInfo.CreateTime
+	reportChapterInfo.ModifyTime = time.Now()
+	reportChapterInfo.ReportCreateTime = time.Now()
+	reportChapterInfo.VideoKind = 2
+	reportChapterInfo.Stage = reportInfo.Stage
+
+	reportChapterInfo.LastModifyAdminId = sysUser.AdminId
+	reportChapterInfo.LastModifyAdminName = sysUser.RealName
+	reportChapterInfo.ContentModifyTime = time.Now()
+	//reportChapterInfo.ContentStruct = html.EscapeString(req.ContentStruct)
+	//reportChapterInfo.CanvasColor = req.CanvasColor
+	//reportChapterInfo.HeadResourceId = req.HeadResourceId
+	//reportChapterInfo.EndResourceId = req.EndResourceId
+
+	err, errMsg := services.AddChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, req.PermissionIdList, req.AdminIdList)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// EditChapterBaseInfoAndPermission
+// @Title 修改报告章节的基础信息、授权用户权限、品种权限
+// @Description 修改报告章节的基础信息、授权用户权限、品种权限
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/base_info/edit [post]
+func (this *ReportController) EditChapterBaseInfoAndPermission() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditReportChapterBaseInfoAndPermissionReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == `` {
+		br.Msg = "章节名称不能为空"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 判断名称是否重复
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND report_id = ? AND title=? AND report_chapter_id != ? "
+		pars = append(pars, reportChapterInfo.ReportId, req.Title, reportChapterId)
+		count, err := models.GetCountReportChapterByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "新增失败"
+			br.ErrMsg = "判断章节名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "章节名称不允许重复"
+			br.ErrMsg = "章节名称不允许重复"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	err, errMsg := services.EditChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, req.Title, req.PermissionIdList, req.AdminIdList)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// EditDayWeekChapter
+// @Title 编辑晨周报章节内容
+// @Description 编辑晨周报章节内容
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editDayWeekChapter [post]
+func (this *ReportController) EditDayWeekChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+	if req.Content == "" {
+		br.Msg = "请输入内容"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+	if reportChapterInfo.PublishState == 2 {
+		br.Msg = "该报告章节已发布,不允许编辑"
+		br.ErrMsg = "该报告章节已发布,不允许编辑"
+		br.IsSendEmail = false
+		return
+	}
+
+	updateCols := make([]string, 0)
+
+	// 报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	reqTickerList := req.TickerList
+	// 更新章节及指标
+	contentSub := ""
+	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
+		}
+		contentClean, e := services.FilterReportContentBr(req.Content)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = contentClean
+
+		contentSub, err = services.GetReportContentSub(req.Content)
+		if err != nil {
+			br.Msg = "内容分段解析失败"
+			br.ErrMsg = "编辑报告章节-解析 ContentSub 失败, Err: " + err.Error()
+			return
+		}
+	}
+
+	if req.Title != `` {
+		reportChapterInfo.Title = req.Title
+		updateCols = append(updateCols, "Title")
+	}
+	//reportChapterInfo.AddType = req.AddType
+	reportChapterInfo.Author = req.Author
+	reportChapterInfo.Content = html.EscapeString(req.Content)
+	reportChapterInfo.ContentSub = html.EscapeString(contentSub)
+	reportChapterInfo.IsEdit = 1
+	reportChapterInfo.ModifyTime = time.Now()
+
+	// 担心外部没有传入时间,如果没有传入,那么就不修改
+	if req.CreateTime != `` {
+		reportChapterInfo.CreateTime = req.CreateTime
+		updateCols = append(updateCols, "CreateTime")
+	}
+
+	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")
+
+	updateCols = append(updateCols, "LastModifyAdminId", "LastModifyAdminName", "ContentModifyTime", "ContentStruct")
+
+	// 章节报告更新指标
+	tickerList := make([]*models.ReportChapterTicker, 0)
+	if len(reqTickerList) > 0 {
+		nowTime := time.Now()
+		for i := 0; i < len(reqTickerList); i++ {
+			tickerList = append(tickerList, &models.ReportChapterTicker{
+				ReportChapterId: reportChapterInfo.ReportChapterId,
+				Sort:            reqTickerList[i].Sort,
+				Ticker:          reqTickerList[i].Ticker,
+				CreateTime:      nowTime,
+				UpdateTime:      nowTime,
+			})
+		}
+	}
+	err = models.UpdateChapterAndTicker(reportInfo, reportChapterInfo, updateCols, tickerList)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 标记更新中
+	{
+		markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			br.Msg = markStatus.Msg
+			return
+		}
+	}
+
+	// 备份关键数据
+	chapters := make([]*models.ReportChapter, 0)
+	chapters = append(chapters, reportChapterInfo)
+	go services.SaveReportLogs(nil, chapters, sysUser.AdminId, sysUser.RealName)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// DelChapter
+// @Title 编辑晨周报章节内容
+// @Description 编辑晨周报章节内容
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/del [post]
+func (this *ReportController) DelChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.DelReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 删除章节
+	err, errMsg = services.DelChapter(reportInfo, reportChapterInfo, sysUser)
+	if err != nil {
+		br.Msg = "删除失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 备份关键数据
+	chapters := make([]*models.ReportChapter, 0)
+	chapters = append(chapters, reportChapterInfo)
+	go services.SaveReportLogs(nil, chapters, sysUser.AdminId, sysUser.RealName)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// GetReportChapterList
+// @Title 获取报告章节列表
+// @Description 获取报告章节列表
+// @Param   ReportId	query	string	true	"报告ID"
+// @Success 200 {object} company.CompanyListResp
+// @router /getReportChapterList [get]
+func (this *ReportController) GetReportChapterList() {
+	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
+	}
+
+	reqReportId := this.GetString("ReportId")
+	reportId, _ := strconv.Atoi(reqReportId)
+	if reportId <= 0 {
+		br.Msg = "报告ID有误"
+		return
+	}
+
+	// 获取报告信息
+	reportInfo, err := models.GetReportByReportId(reportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 权限校验
+	isAuth, err := services.CheckReportAuthByReportId(sysUser, reportInfo.AdminId, reportId)
+	if err != nil {
+		br.Msg = "获取报告权限失败"
+		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		br.Msg = "无操作权限"
+		br.ErrMsg = "无操作权限"
+		return
+	}
+
+	// 获取章节列表
+	chapterList, err := models.GetChapterListByReportId(reportId)
+	if err != nil {
+		br.Msg = "获取章节列表失败"
+		br.ErrMsg = "获取章节列表失败, Err: " + err.Error()
+		return
+	}
+	typeList, err := models.GetReportChapterTypeList()
+	if err != nil {
+		br.Msg = "获取章节类型列表失败"
+		br.ErrMsg = "获取章节类型列表失败, Err: " + err.Error()
+		return
+	}
+	typeIdImg := make(map[int]string)
+	for i := 0; i < len(typeList); i++ {
+		typeIdImg[typeList[i].ReportChapterTypeId] = typeList[i].EditImgUrl
+	}
+
+	resp := make([]models.ReportChapterResp, 0)
+	if len(chapterList) > 0 {
+		chapterIdList := make([]int, 0)
+		// 章节id授权用户列表map
+		chapterIdGrandListMap := make(map[int][]int)
+		// 章节id关联品种id列表map
+		chapterIdPermissionListMap := make(map[int][]int)
+
+		for _, v := range chapterList {
+			chapterIdList = append(chapterIdList, v.ReportChapterId)
+		}
+
+		// 处理章节id授权用户列表
+		{
+			chapterGrantObj := report.ReportChapterGrant{}
+			chapterGrantList, tmpErr := chapterGrantObj.GetGrantListByIdList(chapterIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id授权用户列表失败"
+				br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+				return
+			}
+
+			for _, v := range chapterGrantList {
+				tmpChapterIdGrandList, ok := chapterIdGrandListMap[v.ReportChapterId]
+				if !ok {
+					tmpChapterIdGrandList = make([]int, 0)
+				}
+				chapterIdGrandListMap[v.ReportChapterId] = append(tmpChapterIdGrandList, v.AdminId)
+			}
+		}
+
+		// 处理章节id关联品种id列表
+		{
+			obj := report.ReportChapterPermissionMapping{}
+			chapterPermissionList, tmpErr := obj.GetPermissionListByIdList(chapterIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id关联品种列表失败"
+				br.ErrMsg = "获取章节id关联品种列表失败, Err: " + tmpErr.Error()
+				return
+			}
+
+			for _, v := range chapterPermissionList {
+				tmpChapterIdPermissionList, ok := chapterIdPermissionListMap[v.ReportChapterId]
+				if !ok {
+					tmpChapterIdPermissionList = make([]int, 0)
+				}
+				chapterIdPermissionListMap[v.ReportChapterId] = append(tmpChapterIdPermissionList, v.ChartPermissionId)
+			}
+		}
+
+		// 章节类型的字段
+		for _, item := range chapterList {
+			// 授权的用户列表
+			tmpChapterIdGrandList, ok := chapterIdGrandListMap[item.ReportChapterId]
+			if !ok {
+				tmpChapterIdGrandList = make([]int, 0)
+			}
+			// 关联的品种列表
+			tmpChapterIdPermissionList, ok := chapterIdPermissionListMap[item.ReportChapterId]
+			if !ok {
+				tmpChapterIdPermissionList = make([]int, 0)
+			}
+
+			tmpChapterItem := models.ReportChapterResp{
+				ReportChapterId:  item.ReportChapterId,
+				ReportId:         item.ReportId,
+				ReportType:       item.ReportType,
+				TypeId:           item.TypeId,
+				TypeName:         item.TypeName,
+				TypeEditImg:      typeIdImg[item.TypeId],
+				Title:            item.Title,
+				IsEdit:           item.IsEdit,
+				Trend:            item.Trend,
+				Sort:             item.Sort,
+				PublishState:     item.PublishState,
+				VideoUrl:         item.VideoUrl,
+				VideoName:        item.VideoName,
+				VideoPlaySeconds: item.VideoPlaySeconds,
+				VideoSize:        item.VideoSize,
+				VideoKind:        item.VideoKind,
+				ModifyTime:       item.ModifyTime.Format(utils.FormatDate),
+				GrandAdminIdList: tmpChapterIdGrandList,
+				PermissionIdList: tmpChapterIdPermissionList,
+			}
+			markStatus, err := services.UpdateReportEditMark(item.ReportId, item.ReportChapterId, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+			if err != nil {
+				br.Msg = "查询标记状态失败"
+				br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+				return
+			}
+
+			if markStatus.Status == 0 {
+				tmpChapterItem.CanEdit = true
+			} else {
+				tmpChapterItem.Editor = markStatus.Editor
+			}
+
+			// 报告章节的操作权限
+			tmpChapterItem.IsAuth = services.CheckChapterAuthByAdminIdList(sysUser, reportInfo.AdminId, tmpChapterIdGrandList)
+
+			resp = append(resp, tmpChapterItem)
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// GetDayWeekChapter
+// @Title 获取晨周报章节信息
+// @Description 获取晨周报章节信息
+// @Param	ReportChapterId  query  int  true  "报告章节ID"
+// @Success 200 Ret=200 保存成功
+// @router /getDayWeekChapter [get]
+func (this *ReportController) GetDayWeekChapter() {
+	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
+	}
+
+	reportChapterId, _ := this.GetInt("ReportChapterId")
+	if reportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	chapterItem, err := models.GetReportChapterItemById(reportChapterId)
+	if err != nil {
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportById(chapterItem.ReportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 权限校验
+	isAuth, err := services.CheckReportAuthByReportId(sysUser, reportInfo.AdminId, reportInfo.Id)
+	if err != nil {
+		br.Msg = "获取报告权限失败"
+		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		br.Msg = "无操作权限"
+		br.ErrMsg = "无操作权限"
+		return
+	}
+
+	chapterItem.Content = html.UnescapeString(chapterItem.Content)
+	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
+	chapterPermissionIdList := make([]int, 0)
+	// 处理章节id授权用户列表
+	{
+		chapterGrantObj := report.ReportChapterGrant{}
+		chapterGrantList, tmpErr := chapterGrantObj.GetGrantListById(chapterItem.ReportChapterId)
+		if tmpErr != nil {
+			br.Msg = "获取章节id授权用户列表失败"
+			br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+			return
+		}
+
+		for _, v := range chapterGrantList {
+			chapterGrantIdList = append(chapterGrantIdList, v.AdminId)
+		}
+	}
+
+	// 处理章节id关联品种id列表
+	{
+		obj := report.ReportChapterPermissionMapping{}
+		chapterPermissionList, tmpErr := obj.GetPermissionListById(chapterItem.ReportChapterId)
+		if tmpErr != nil {
+			br.Msg = "获取章节id关联品种列表失败"
+			br.ErrMsg = "获取章节id关联品种列表失败, Err: " + tmpErr.Error()
+			return
+		}
+
+		for _, v := range chapterPermissionList {
+			chapterPermissionIdList = append(chapterPermissionIdList, v.ChartPermissionId)
+		}
+	}
+
+	resp := models.ReportChapterItemResp{
+		ReportChapterItem: *chapterItem,
+		GrandAdminIdList:  chapterGrantIdList,
+		PermissionIdList:  chapterPermissionIdList,
+	}
+
+	// 获取当前编辑状态
+	{
+		markStatus, err := services.UpdateReportEditMark(chapterItem.ReportId, chapterItem.ReportChapterId, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = "查询标记状态失败"
+			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+			return
+		}
+		if markStatus.Status == 0 {
+			resp.CanEdit = true
+		} else {
+			resp.Editor = markStatus.Editor
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ChapterMove
+// @Title 移动章节类型
+// @Description 移动章节类型
+// @Param	request	body models.PermissionMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /chapter/move [post]
+func (this *ReportController) ChapterMove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.ReportChapterMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.ReportChapterId == 0 {
+		br.Msg = "请选择要移动的章节"
+		return
+	}
+	e, msg := services.MoveReportChapter(&req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// EditChapterTrendTag
+// @Title 编辑章节趋势标签
+// @Description 编辑章节趋势标签
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editChapterTrendTag [post]
+func (this *ReportController) EditChapterTrendTag() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditChapterTrendTagReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "章节ID有误"
+		return
+	}
+	if req.Trend == "" {
+		br.Msg = "请输入标签"
+		return
+	}
+
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(chapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, chapterInfo, false, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 更新章节标签
+	chapterInfo.Trend = req.Trend
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Trend")
+	if err = chapterInfo.UpdateChapter(updateCols); err != nil {
+		br.Msg = "更新标签失败"
+		br.ErrMsg = "更新标签失败, Err: " + err.Error()
+		return
+	}
+
+	// 添加关键词
+	if err = models.AddTrendTagKeyWord(req.Trend); err != nil {
+		br.Msg = "添加标签关键词失败"
+		br.ErrMsg = "添加标签关键词失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// GetSunCode 获取太阳码
+// @Title 公共模块
+// @Description 获取分享海报
+// @Param	request	body models.SunCodeReq true "type json string"
+// @Success 200 {object} string "获取成功"
+// @failure 400 {string} string "获取失败"
+// @router /getSunCode [post]
+func (this *ReportController) GetSunCode() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.SunCodeReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	var sunCodeUrl string
+	//先查,查不到再去生成上传
+	item, err := models.GetYbPcSunCode(req.CodeScene, req.CodePage)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "查询太阳码失败!"
+		br.ErrMsg = "查询太阳码失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		sunCodeUrl = item.SuncodeUrl
+	}
+
+	if sunCodeUrl == "" {
+		sunCodeUrl, err = services.PcCreateAndUploadSunCode(req.CodeScene, req.CodePage)
+		if err != nil {
+			br.Msg = "生成太阳码失败!"
+			br.ErrMsg = "生成太阳码失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	br.Data = sunCodeUrl
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// VoiceUpload
+// @Title 音频上传
+// @Description 音频上传接口
+// @Param   file   query   file  true       "文件"
+// @Param   ReportChapterId   query   int  true       "报告章节ID"
+// @Success Ret=200 上传成功
+// @router /chapter/voice/upload [post]
+func (this *ReportController) VoiceUpload() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	f, h, err := this.GetFile("file")
+	if err != nil {
+		br.Msg = "获取资源信息失败"
+		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 报告章节id
+	reportChapterId, err := this.GetInt("ReportChapterId", 0)
+	if err != nil {
+		br.Msg = "报告章节ID异常"
+		br.ErrMsg = "报告章节ID异常,Err:" + err.Error()
+		return
+	}
+
+	// 报告章节信息
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "获取报告章节信息失败"
+		br.ErrMsg = "获取报告章节信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 报告信息
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 权限校验
+	isAuth, err := services.CheckChapterAuthByReportChapterInfo(this.SysUser, reportInfo.AdminId, reportChapterInfo)
+	if err != nil {
+		br.Msg = "获取报告权限失败"
+		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		br.Msg = "无操作权限"
+		br.ErrMsg = "无操作权限"
+		return
+	}
+
+	ext := path.Ext(h.Filename)
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "hongze/" + dateDir
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		br.Msg = "存储目录创建失败"
+		br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName := randStr + ext
+	fPath := uploadDir + "/" + fileName
+	defer f.Close() //关闭上传文件
+	err = this.SaveToFile("file", fPath)
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	resourceUrl := ``
+	//上传到阿里云 和 minio
+	//if utils.ObjectStorageClient == "minio" {
+	//	resourceUrl, err = services.UploadAudioToMinIo(fileName, fPath)
+	//	if err != nil {
+	//		br.Msg = "文件上传失败"
+	//		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+	//		return
+	//	}
+	//} else {
+	//	resourceUrl, err = services.UploadAudioAliyun(fileName, fPath)
+	//	if err != nil {
+	//		br.Msg = "文件上传失败"
+	//		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		br.Msg = "上传失败"
+		br.ErrMsg = "初始化OSS服务失败"
+		return
+	}
+	resourceUrl, err = ossClient.UploadFile(fileName, fPath, "")
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	defer func() {
+		os.Remove(fPath)
+	}()
+	item := new(models.Resource)
+	item.ResourceUrl = resourceUrl
+	item.ResourceType = 2
+	item.CreateTime = time.Now()
+	newId, err := models.AddResource(item)
+	if err != nil {
+		br.Msg = "资源上传失败"
+		br.ErrMsg = "资源上传失败,Err:" + err.Error()
+		return
+	}
+
+	//var playSeconds float64
+	//playSeconds, err = mp3duration.Calculate(fPath)
+	//if playSeconds <= 0 {
+	//	playSeconds, err = utils.GetVideoPlaySeconds(fPath)
+	//	if err != nil {
+	//		br.Msg = "获取音频时间失败"
+	//		br.ErrMsg = "获取音频时间失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
+	playSecondsStr, err := utils.GetDuration(fPath) //mp3duration.Calculate(fPath)
+	if err != nil {
+		utils.FileLog.Info("获取音频时间失败,Err:" + err.Error())
+	}
+
+	fileBody, err := os.ReadFile(fPath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	{
+		reportChapterCreateTime, err := time.Parse(utils.FormatDateTime, reportChapterInfo.CreateTime)
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "上传失败,Err:" + err.Error()
+			return
+		}
+		createTimeStr := reportChapterCreateTime.Format("0102")
+		videoName := reportChapterInfo.Title + "(" + createTimeStr + ")"
+
+		reportChapterInfo.VideoUrl = resourceUrl
+		reportChapterInfo.VideoName = videoName
+		reportChapterInfo.VideoPlaySeconds = playSecondsStr
+		reportChapterInfo.VideoSize = sizeStr
+		reportChapterInfo.VideoKind = 1
+		reportChapterInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportChapterInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.VoiceGenerateType = 1
+		reportChapterInfo.ModifyTime = time.Now()
+		err = reportChapterInfo.UpdateChapter([]string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "VideoKind", "LastModifyAdminId", "LastModifyAdminName", "VoiceGenerateType", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告章节的音频信息失败,Err:" + err.Error()
+			return
+		}
+
+		// 修改报告的最近更新人信息
+		reportInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告最后更新人信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	// 处理报告中的音频文件分贝
+	go services.HandleVideoDecibel(reportChapterInfo)
+
+	resp := new(models.ResourceResp)
+	resp.Id = newId
+	resp.ResourceUrl = resourceUrl
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}
+
+// PublishDayWeekReportChapter
+// @Title 发布章节
+// @Description 发布章节
+// @Param	request	body models.PublishReportChapterReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /publishDayWeekReportChapter [post]
+func (this *ReportController) PublishDayWeekReportChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.PublishReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	// 获取报告详情
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "章节信息有误"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	reportInfo, err := models.GetReportByReportId(chapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "查询报告有误"
+		br.ErrMsg = "查询报告信息失败, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 图表刷新状态
+	refreshResult := data.CheckBatchChartRefreshResult("report", chapterInfo.ReportId, chapterInfo.ReportChapterId)
+	if !refreshResult {
+		br.Msg = "图表刷新未完成,请稍后操作"
+		br.ErrMsg = "图表刷新未完成,请稍后操作"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 如果是系统章节,那么需要校验下是否已经停更
+	if chapterInfo.TypeId > 0 {
+		// 获取规则配置
+		reportChapterTypeRule, err := models.GetReportChapterTypeById(chapterInfo.TypeId)
+		if err != nil {
+			br.Msg = "获取配置信息异常"
+			br.ErrMsg = "获取配置信息异常, Err: " + err.Error()
+			return
+		}
+		if reportChapterTypeRule.Enabled == 0 {
+			br.Msg = "该章节已永久停更"
+			br.ErrMsg = "该章节已永久停更 "
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	var publishTime time.Time
+	if reportInfo.MsgIsSend == 1 && reportInfo.PublishTime.IsZero() { //如果报告曾经发布过,并且已经发送过模版消息,则章节的发布时间为报告的发布时间
+		publishTime = reportInfo.PublishTime
+	} else {
+		publishTime = time.Now()
+	}
+
+	// 更新报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+	err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "报告最后编辑人保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 更新章节信息
+	chapterInfo.IsEdit = 1
+	chapterInfo.PublishState = 2
+	chapterInfo.PublishTime = publishTime
+	chapterInfo.LastModifyAdminId = this.SysUser.AdminId
+	chapterInfo.LastModifyAdminName = this.SysUser.RealName
+	chapterInfo.ModifyTime = time.Now()
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "IsEdit", "PublishState", "PublishTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+	err = chapterInfo.UpdateChapter(updateCols)
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 修改报告的最近更新人信息
+	go func() {
+		reportInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告最后更新人信息失败,Err:" + err.Error()
+			return
+		}
+	}()
+
+	// 更新章节ES
+	{
+		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+	}
+
+	// 同时发布报告
+	//if req.PublishReport == 1 {
+	//	if _, e := services.PublishDayWeekReport(chapterInfo.ReportId); e != nil {
+	//		br.Msg = "章节发布成功,报告发布失败,请手动发布报告"
+	//		br.ErrMsg = "发布晨/周报失败, Err: " + e.Error()
+	//		return
+	//	}
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// GetUnPublishReportChapterList
+// @Title 获取报告中未发布的章节数
+// @Description 获取报告中未发布的章节数
+// @Param	ReportId  query  int  true  "报告ID"
+// @Success 200 Ret=200 获取成功
+// @router /chapter/un_publish/list [get]
+func (this *ReportController) GetUnPublishReportChapterList() {
+	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
+	}
+
+	reportId, _ := this.GetInt("ReportId")
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.IsSendEmail = false
+		return
+	}
+	reportInfo, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "报告有误"
+		br.ErrMsg = "报告有误, Err: " + err.Error()
+		return
+	}
+
+	if reportInfo.HasChapter == 0 {
+		br.Msg = "报告未包含章节"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取未发布的章节列表
+	unPublishedList, err := models.GetUnPublishedChapterList(reportInfo.Id)
+	if err != nil {
+		br.Msg = "获取未发布的章节数失败"
+		br.ErrMsg = "获取未发布的章节数失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = unPublishedList
+}
+
+// EditChapterTitle
+// @Title 编辑章节标题
+// @Description 编辑章节标题
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/title/edit [post]
+func (this *ReportController) EditChapterTitle() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == `` {
+		br.Msg = "章节名称不能为空"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 判断名称是否重复
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND report_id = ? AND title=? AND report_chapter_id != ? "
+		pars = append(pars, reportChapterInfo.ReportId, req.Title, reportChapterId)
+		count, err := models.GetCountReportChapterByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "新增失败"
+			br.ErrMsg = "判断章节名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "章节名称不允许重复"
+			br.ErrMsg = "章节名称不允许重复"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
+	if !hasAuth {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+	// 报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	reportChapterInfo.Title = req.Title
+	reportChapterInfo.LastModifyAdminId = sysUser.AdminId
+	reportChapterInfo.LastModifyAdminName = sysUser.RealName
+	reportChapterInfo.ModifyTime = time.Now()
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Title", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+
+	// 更新报告的最近编辑人信息
+	if err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}); err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+	// 更新章节
+	if err = reportChapterInfo.UpdateChapter(updateCols); err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+	// 标记更新中
+	{
+		markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			br.Msg = markStatus.Msg
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// CancelPublishReportChapter
+// @Title 取消发布章节
+// @Description 取消发布章节
+// @Param	request	body models.PublishReportChapterReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /chapter/publish/cancel [post]
+func (this *ReportController) CancelPublishReportChapter() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.PublishReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	// 获取章节详情
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "章节信息有误"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+	// 章节发布状态校验
+	if chapterInfo.PublishState == 1 {
+		br.Msg = "该章节未发布,不能重复撤销"
+		br.ErrMsg = "该章节未发布,不能重复撤销"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(chapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "查询报告有误"
+		br.ErrMsg = "查询报告信息失败, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许撤销章节"
+		br.ErrMsg = "该报告已发布,不允许撤销章节"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 更新章节信息
+	chapterInfo.PublishState = 1
+	chapterInfo.PublishTime = time.Time{}
+	chapterInfo.LastModifyAdminId = this.SysUser.AdminId
+	chapterInfo.LastModifyAdminName = this.SysUser.RealName
+	chapterInfo.ModifyTime = time.Now()
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "PublishState", "PublishTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+	err = chapterInfo.UpdateChapter(updateCols)
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 更新章节ES
+	{
+		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "撤销成功"
+}
+
+// checkOpPermission
+// @Description: 操作权限校验
+// @author: Roc
+// @datetime 2024-11-12 09:58:34
+// @param sysUser *system.Admin
+// @param reportInfo *models.Report
+// @param reportChapterInfo *models.ReportChapter
+// @param isMarkStatus bool
+// @param lang string
+// @return hasAuth bool
+// @return msg string
+// @return errMsg string
+// @return isSendEmail bool
+func checkOpPermission(sysUser *system.Admin, reportInfo *models.Report, reportChapterInfo *models.ReportChapter, isMarkStatus bool, lang string) (hasAuth bool, msg, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	// 权限校验
+	isAuth, err := services.CheckChapterAuthByReportChapterInfo(sysUser, reportInfo.AdminId, reportChapterInfo)
+	if err != nil {
+		msg = "获取报告权限失败"
+		errMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		msg = "没有权限"
+		errMsg = "没有权限"
+		isSendEmail = false
+		return
+	}
+
+	// 如果不是创建人,那么就要去查看是否授权
+	//if reportInfo.AdminId != sysUser.AdminId && !utils.IsAdminRole(sysUser.RoleTypeCode) {
+	//	// 授权用户权限校验
+	//	chapterGrantObj := report.ReportChapterGrant{}
+	//	_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
+	//	if tmpErr != nil {
+	//		if tmpErr.Error() == utils.ErrNoRow() {
+	//			msg = "没有权限"
+	//			errMsg = "没有权限"
+	//			isSendEmail = false
+	//			return
+	//		}
+	//		msg = "获取章节id授权用户失败"
+	//		errMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
+	//		return
+	//	}
+	//}
+
+	// 标记更新中
+	if isMarkStatus {
+		markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, lang)
+		if err != nil {
+			msg = err.Error()
+			errMsg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			msg = markStatus.Msg
+			errMsg = markStatus.Msg
+			isSendEmail = false
+			return
+		}
+	}
+
+	// 有权限
+	hasAuth = true
+
+	return
+}

+ 50 - 63
controllers/report_chapter_type.go

@@ -16,7 +16,7 @@ type ReportChapterTypeController struct {
 // List
 // @Title 报告章节列表
 // @Description 报告章节列表
-// @Param   ReportType  query  string  true  "报告类型: day-晨报; week-周报"
+// @Param   ClassifyId  query  int  true  "所属分类id"
 // @Success 200 {object} models.ReportChapterTypePageListResp
 // @router /chapter_type/list [get]
 func (this *ReportChapterTypeController) List() {
@@ -33,22 +33,27 @@ func (this *ReportChapterTypeController) List() {
 		br.Ret = 408
 		return
 	}
-	reportType := this.GetString("ReportType")
-	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
-	if !utils.InArrayByStr(typeArr, reportType) {
-		br.Msg = "请选择报告类型"
+	classifyId, _ := this.GetInt("ClassifyId", 0)
+	if classifyId <= 0 {
+		br.Msg = "请选择分类"
 		return
 	}
-	cond := ` AND research_type = ?`
+
+	cond := ` AND report_classify_id = ?`
 	pars := make([]interface{}, 0)
-	pars = append(pars, reportType)
+	pars = append(pars, classifyId)
 	list, e := models.GetReportChapterTypePageList(cond, pars)
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取报告章节列表失败, Err: " + e.Error()
 		return
 	}
-	mappingList, e := models.GetChapterTypePermissionByResearchType(reportType)
+	chapterTypeIdList := make([]int, 0)
+	for _, v := range list {
+		chapterTypeIdList = append(chapterTypeIdList, v.ReportChapterTypeId)
+	}
+
+	mappingList, e := models.GetChapterTypePermissionByChapterTypeIdList(chapterTypeIdList)
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取章节类型权限列表失败, Err: " + e.Error()
@@ -116,15 +121,16 @@ func (this *ReportChapterTypeController) Add() {
 		br.Msg = "请输入章节名称"
 		return
 	}
-	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
-	if !utils.InArrayByStr(typeArr, req.ResearchType) {
-		br.Msg = "请选择报告类型"
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
 		return
 	}
+
 	// 重名校验
-	cond := ` AND report_chapter_type_name = ? AND research_type = ?`
+	cond := ` AND report_chapter_type_name = ? AND report_classify_id = ?`
 	pars := make([]interface{}, 0)
-	pars = append(pars, req.ReportChapterTypeName, req.ResearchType)
+	pars = append(pars, req.ReportChapterTypeName, req.ClassifyId)
 	exists, e := models.GetReportChapterTypeByCondition(cond, pars)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -149,7 +155,9 @@ func (this *ReportChapterTypeController) Add() {
 	item.Enabled = 1
 	item.CreatedTime = nowTime
 	item.LastUpdatedTime = nowTime
-	item.ResearchType = req.ResearchType
+	//item.ResearchType = req.ResearchType
+	item.ResearchType = "chapter"
+	item.ReportClassifyId = req.ClassifyId
 	item.IsSet = 0
 	item.ReportChapterTypeKey = req.ReportChapterTypeName
 	item.TickerTitle = req.ReportChapterTypeName
@@ -160,7 +168,6 @@ func (this *ReportChapterTypeController) Add() {
 		return
 	}
 
-	// todo 更新章节权限
 	cond = ` and product_id=1`
 	pars = make([]interface{}, 0)
 	permissionList, e := services.GetChartPermissionList(cond, pars)
@@ -174,29 +181,20 @@ func (this *ReportChapterTypeController) Add() {
 		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
 	}
 
-	researchType := item.ResearchType
-	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
-	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	newPermissions := make([]*models.ReportChapterTypePermission, 0) // 报告章节权限表(新)
 	for i := range req.ChartPermissionIdList {
 		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
 			ReportChapterTypeId:   item.ReportChapterTypeId,
 			ReportChapterTypeName: item.ReportChapterTypeName,
 			ChartPermissionId:     req.ChartPermissionIdList[i],
 			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
-			ResearchType:          researchType,
+			ResearchType:          item.ResearchType,
 			CreatedTime:           nowTime,
 		})
-		if researchType == utils.REPORT_TYPE_WEEK {
-			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
-				ChartPermissionId:   req.ChartPermissionIdList[i],
-				ReportChapterTypeId: item.ReportChapterTypeId,
-				ResearchType:        researchType,
-			})
-		}
 	}
 
 	// 设置权限
-	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, newPermissions)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
@@ -208,10 +206,9 @@ func (this *ReportChapterTypeController) Add() {
 		_ = utils.Rc.Delete(key)
 	}
 
-	//todo 同步更新crm章节权限和章节类型
 	go func() {
 		var syncReq services.ChapterTypeSyncReq
-		syncReq.ResearchType = researchType
+		syncReq.ResearchType = item.ResearchType
 		syncReq.ReportChapterTypeId = item.ReportChapterTypeId
 		_, _ = services.ReportChapterTypeSync(&syncReq)
 	}()
@@ -254,15 +251,16 @@ func (this *ReportChapterTypeController) Edit() {
 		br.Msg = "请输入章节名称"
 		return
 	}
-	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
-	if !utils.InArrayByStr(typeArr, req.ResearchType) {
-		br.Msg = "请选择报告类型"
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
 		return
 	}
+
 	// 重名校验
-	cond := ` AND report_chapter_type_name = ? AND research_type = ?`
+	cond := ` AND report_chapter_type_name = ? AND report_classify_id = ?`
 	pars := make([]interface{}, 0)
-	pars = append(pars, req.ReportChapterTypeName, req.ResearchType)
+	pars = append(pars, req.ReportChapterTypeName, req.ClassifyId)
 	exists, e := models.GetReportChapterTypeByCondition(cond, pars)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -281,8 +279,15 @@ func (this *ReportChapterTypeController) Edit() {
 		return
 	}
 	originName := item.ReportChapterTypeName
-	//更新章节权限
-	// todo 更新章节权限
+	if item.ReportChapterTypeName != req.ReportChapterTypeName {
+		item.ReportChapterTypeName = req.ReportChapterTypeName
+		err := item.Update([]string{"ReportChapterTypeName"})
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改章节类型失败, Err: " + err.Error()
+			return
+		}
+	}
 	cond = ` and product_id=1`
 	pars = make([]interface{}, 0)
 	permissionList, e := services.GetChartPermissionList(cond, pars)
@@ -297,29 +302,20 @@ func (this *ReportChapterTypeController) Edit() {
 		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
 	}
 
-	researchType := item.ResearchType
-	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
-	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	newPermissions := make([]*models.ReportChapterTypePermission, 0) // 报告章节权限表(新)
 	for i := range req.ChartPermissionIdList {
 		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
 			ReportChapterTypeId:   item.ReportChapterTypeId,
 			ReportChapterTypeName: item.ReportChapterTypeName,
 			ChartPermissionId:     req.ChartPermissionIdList[i],
 			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
-			ResearchType:          researchType,
+			ResearchType:          item.ResearchType,
 			CreatedTime:           nowTime,
 		})
-		if researchType == utils.REPORT_TYPE_WEEK {
-			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
-				ChartPermissionId:   req.ChartPermissionIdList[i],
-				ReportChapterTypeId: item.ReportChapterTypeId,
-				ResearchType:        researchType,
-			})
-		}
 	}
 
 	// 设置权限
-	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, newPermissions)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
@@ -340,7 +336,7 @@ func (this *ReportChapterTypeController) Edit() {
 
 	go func() {
 		var syncReq services.ChapterTypeSyncReq
-		syncReq.ResearchType = researchType
+		syncReq.ResearchType = item.ResearchType
 		syncReq.ReportChapterTypeId = item.ReportChapterTypeId
 		_, _ = services.ReportChapterTypeSync(&syncReq)
 	}()
@@ -474,30 +470,21 @@ func (this *ReportChapterTypeController) AuthSetting() {
 		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
 	}
 
-	researchType := item.ResearchType
 	nowTime := time.Now().Local()
-	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
-	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	newPermissions := make([]*models.ReportChapterTypePermission, 0) // 报告章节权限表(新)
 	for i := range req.ChartPermissionIdList {
 		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
 			ReportChapterTypeId:   item.ReportChapterTypeId,
 			ReportChapterTypeName: item.ReportChapterTypeName,
 			ChartPermissionId:     req.ChartPermissionIdList[i],
 			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
-			ResearchType:          researchType,
-			CreatedTime:           nowTime,
+			//ResearchType:          researchType,
+			CreatedTime: nowTime,
 		})
-		if researchType == utils.REPORT_TYPE_WEEK {
-			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
-				ChartPermissionId:   req.ChartPermissionIdList[i],
-				ReportChapterTypeId: item.ReportChapterTypeId,
-				ResearchType:        researchType,
-			})
-		}
 	}
 
 	// 设置权限
-	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, newPermissions)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
@@ -546,7 +533,7 @@ func (this *ReportChapterTypeController) PermissionList() {
 		br.Msg = "章节不存在或已被删除"
 		return
 	}
-	list, e := models.GetChapterTypePermissionByTypeIdAndResearchType(typeId, item.ResearchType)
+	list, e := models.GetChapterTypePermissionByReportChapterTypeId(typeId)
 	if e != nil {
 		br.Msg = "章节不存在或已被删除"
 		br.ErrMsg = "获取章节类型权限列表失败, Err: " + e.Error()

+ 2001 - 0
controllers/report_v2.go

@@ -0,0 +1,2001 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/report"
+	"eta/eta_mobile/models/report_approve"
+	"eta/eta_mobile/models/smart_report"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/services"
+	"eta/eta_mobile/services/alarm_msg"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ListReport
+// @Title 获取报告列表接口
+// @Description 获取报告列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   TimeType     query string true  "筛选的时间类别:publish_time(发布时间),modify_time(更新时间);approve_time(审批时间)"
+// @Param   StartDate   query   string  true       "开始时间"
+// @Param   EndDate   query   string  true       "结束时间"
+// @Param   Frequency   query   string  true       "频度"
+// @Param   ClassifyIdFirst   query   int  true       "一级分类id"
+// @Param   ClassifyIdSecond   query   int  true       "二级分类id"
+// @Param   ClassifyIdThird   query   int  true       "三级分类id"
+// @Param   State   query   int  true       "状态"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   PublishSort   query   string  true       "desc:降序,asc 升序(预留)"
+// @Param   FilterReportType   query   string  true       "筛选报告类型,1:公共研报,2:共享研报,3:我的研报"
+// @Success 200 {object} models.ReportListResp
+// @router /list [get]
+func (this *ReportController) ListReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	timeType := this.GetString("TimeType")
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	frequency := this.GetString("Frequency")
+	classifyIdFirst, _ := this.GetInt("ClassifyIdFirst", 0)
+	classifyIdSecond, _ := this.GetInt("ClassifyIdSecond", 0)
+	classifyIdThird, _ := this.GetInt("ClassifyIdThird", 0)
+	state, _ := this.GetInt("State")
+	keyWord := this.GetString("KeyWord")
+	msgIsSend, _ := this.GetInt("MsgIsSend")
+	filterReportType, _ := this.GetInt("FilterReportType", 1)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	if timeType == "" {
+		timeType = "publish_time"
+	}
+	if timeType != "publish_time" && timeType != "modify_time" && timeType != "approve_time" {
+		br.Msg = "请选择正确的时间"
+		br.ErrMsg = "请选择正确的时间"
+		return
+	}
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND (a.title LIKE ? OR a.admin_real_name LIKE ? ) `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
+	}
+	if startDate != "" {
+		condition += ` AND a.` + timeType + ` >= ? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		condition += ` AND a.` + timeType + ` <= ? `
+		pars = append(pars, endDate)
+	}
+	if frequency != "" {
+		condition += ` AND a.frequency = ? `
+		pars = append(pars, frequency)
+	}
+
+	if classifyIdFirst > 0 {
+		condition += ` AND a.classify_id_first = ? `
+		pars = append(pars, classifyIdFirst)
+	}
+	if classifyIdSecond > 0 {
+		condition += ` AND a.classify_id_second = ? `
+		pars = append(pars, classifyIdSecond)
+	}
+	if classifyIdThird > 0 {
+		condition += ` AND a.classify_id_third = ? `
+		pars = append(pars, classifyIdThird)
+	}
+
+	if state > 0 {
+		condition += ` AND a.state = ? `
+		pars = append(pars, state)
+	}
+	// 消息是否已推送 1-未推送; 2-已推送
+	if msgIsSend == 1 {
+		condition += ` AND (a.msg_is_send = 0 OR a.ths_msg_is_send = 0) `
+	}
+	if msgIsSend == 2 {
+		condition += ` AND a.msg_is_send = 1 AND a.ths_msg_is_send = 1 `
+	}
+
+	var err error
+	var total int
+	var list []*models.ReportList
+
+	switch filterReportType {
+	// 筛选报告类型,1:公共研报,2:共享研报,3:我的研报
+	case 1:
+		condition += ` AND a.is_public_publish = ? `
+		pars = append(pars, 1)
+		condition += `  AND a.state in (2,6) `
+
+	case 3:
+		// 如果不是超管,那么就看自己有权限的
+		if !utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+			condition += ` AND a.admin_id = ? `
+			pars = append(pars, this.SysUser.AdminId)
+		}
+	case 2:
+		// 如果不是超管,那么就看自己有权限的
+		if !utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+			condition += ` AND (a.admin_id = ? or b.admin_id = ?) `
+			pars = append(pars, this.SysUser.AdminId, this.SysUser.AdminId)
+		}
+	}
+
+	// 共享报告需要连表查询,所以需要单独写
+	if filterReportType == 2 {
+		total, err = models.GetReportListCountByGrant(condition, pars)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		list, err = models.GetReportListByGrant(condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		total, err = models.GetReportListCountV1(condition, pars)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		list, err = models.GetReportListV1(condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	listLen := len(list)
+	if listLen > 0 {
+		pvMap := make(map[int]int)
+		uvMap := make(map[int]int)
+		reportIdArr := make([]int, 0)
+		syncReportIdArr := make([]string, 0)      // 同步过来的报告IDs
+		oldAndNewReportIdMap := make(map[int]int) // 旧报告和新报告的id对应关系
+		for i := 0; i < listLen; i++ {
+			reportIdArr = append(reportIdArr, list[i].Id)
+			if list[i].OldReportId > 0 && list[i].ReportLayout == 1 {
+				syncReportIdArr = append(syncReportIdArr, strconv.Itoa(list[i].OldReportId))
+				oldAndNewReportIdMap[list[i].OldReportId] = list[i].Id
+			}
+			pvMap[list[i].Id] = list[i].Pv
+			uvMap[list[i].Id] = list[i].Uv
+		}
+
+		// 当下报告的pv,uv
+		if len(reportIdArr) > 0 {
+			pvList, e := models.GetReportPvUvByReportIdList(reportIdArr)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取同步报告对应的PV、UV失败, Err: " + e.Error()
+				return
+			}
+			for _, v := range pvList {
+				pv := pvMap[v.ReportId]
+				uv := uvMap[v.ReportId]
+				pvMap[v.ReportId] = v.PvTotal + pv
+				uvMap[v.ReportId] = v.UvTotal + uv
+			}
+		}
+
+		//reportIds := strings.Join(reportIdArr, ",")
+		//syncReportIds := strings.Join(syncReportIdArr, ",")
+
+		// 查询同步过来的报告对应的老报告PV+UV
+		if len(syncReportIdArr) > 0 {
+			puvList, e := models.GetPUVByResearchReportIds(syncReportIdArr)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取同步报告对应的PV、UV失败, Err: " + e.Error()
+				return
+			}
+			puvLen := len(puvList)
+			for i := 0; i < puvLen; i++ {
+				newReportId, ok := oldAndNewReportIdMap[puvList[i].ResearchReportId]
+				if ok {
+					pv := pvMap[newReportId]
+					uv := uvMap[newReportId]
+					pvMap[newReportId] = puvList[i].Pv + pv
+					uvMap[newReportId] = puvList[i].Uv + uv
+				}
+			}
+		}
+		// 晨周报音频列表
+		videoList, err := models.GetReportChapterVideoListByReportIds(reportIdArr)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取报告音频文件失败,Err:" + err.Error()
+			return
+		}
+		for i := 0; i < listLen; i++ {
+			list[i].Content = html.UnescapeString(list[i].Content)
+			list[i].ContentSub = html.UnescapeString(list[i].ContentSub)
+			// 除周报外其余报告均可推送客群
+			list[i].NeedThsMsg = 1
+			//if list[i].HasChapter == 1 && list[i].ChapterType == utils.REPORT_TYPE_WEEK {
+			//	list[i].NeedThsMsg = 0
+			//}
+			chapterList := make([]*models.ReportChapterVideoList, 0)
+			for ii := 0; ii < len(videoList); ii++ {
+				if list[i].Id == videoList[ii].ReportId {
+					chapterList = append(chapterList, videoList[ii])
+				}
+			}
+			list[i].ChapterVideoList = chapterList
+			list[i].Pv = pvMap[list[i].Id]
+			list[i].Uv = uvMap[list[i].Id]
+		}
+
+		// 多人协作的协作报告,需要判断是否可编辑
+		{
+			grantObj := report.ReportGrant{}
+			grantList, err := grantObj.GetGrantListByIdList(reportIdArr)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取报告授权失败,Err:" + err.Error()
+				return
+			}
+
+			//grantMap := make(map[报告id]map[用户id]bool)
+			grantMap := make(map[int]map[int]bool)
+			for _, v := range grantList {
+				grantUserMap, ok := grantMap[v.ReportId]
+				if !ok {
+					grantUserMap = make(map[int]bool)
+				}
+				grantUserMap[v.AdminId] = true
+			}
+
+			for i, item := range list {
+				if item.AdminId == this.SysUser.AdminId {
+					list[i].HasAuth = true
+					continue
+				}
+
+				// 如果是超管,那么有权限
+				if utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+					list[i].HasAuth = true
+					continue
+				}
+
+				// 查找授权
+				var hasAuth bool
+				grantUserMap, ok := grantMap[item.Id]
+
+				// 如果报告根本没有授权用户,说明没有授权当前用户
+				if !ok {
+					continue
+				}
+				_, ok = grantUserMap[this.SysUser.AdminId]
+				list[i].HasAuth = hasAuth
+				// 如果报告关联用户找到,说明有授权当前用户
+				if ok {
+					list[i].HasAuth = true
+				}
+			}
+
+		}
+	}
+
+	for _, item := range list {
+		/*key := fmt.Sprint(`crm:report:edit:`, item.Id)
+		opUserId, _ := utils.Rc.RedisInt(key)
+		//如果当前没有人操作,获取当前操作人是本人,那么编辑按钮可用
+		if opUserId <= 0 || (opUserId == this.SysUser.AdminId) || item.ClassifyNameFirst == "周报" || item.ClassifyNameFirst == "晨报" {
+			item.CanEdit = true
+		} else {
+			adminInfo, errAdmin := system.GetSysUserById(opUserId)
+			if errAdmin != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + errAdmin.Error()
+				return
+			}
+			item.Editor = adminInfo.RealName
+		}*/
+		if item.HasChapter == 1 {
+			item.CanEdit = true
+			continue
+		}
+		markStatus, err := services.UpdateReportEditMark(item.Id, 0, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = "查询标记状态失败"
+			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+			return
+		}
+		if markStatus.Status == 0 {
+			item.CanEdit = true
+		} else {
+			item.Editor = markStatus.Editor
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ReportListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增报告接口
+// @Description 新增报告(不区分报告类型)
+// @Param	request	body models.AddReq true "type json string"
+// @Success 200 {object} models.AddResp
+// @router /add [post]
+func (this *ReportController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.AddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.Title == `` {
+		br.Msg = "标题不能为空"
+		br.ErrMsg = "标题不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ClassifyIdFirst <= 0 {
+		br.Msg = "分类必填"
+		br.ErrMsg = "分类必填"
+		br.IsSendEmail = false
+		return
+	}
+
+	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)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = content
+
+		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)
+		}
+	}
+
+	if req.ContentStruct != `` {
+		req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+	}
+
+	// 报告期数
+	maxStage, err := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
+	if err != nil {
+		br.Msg = "期数获取失败!"
+		br.ErrMsg = "期数获取失败,Err:" + err.Error()
+		return
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird, models.ReportOperateAdd)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 协作方式,1:个人,2:多人协作。默认:1
+	if req.CollaborateType == 0 {
+		req.CollaborateType = 1
+	}
+	// 报告布局,1:常规布局,2:智能布局。默认:1
+	if req.ReportLayout == 0 {
+		req.ReportLayout = 1
+	}
+	// 是否公开发布,1:是,2:否
+	if req.IsPublicPublish == 0 {
+		req.IsPublicPublish = 1
+	}
+
+	classifyItemList, err := models.GetClassifyListByIdList([]int{req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird})
+	classifyMap := make(map[int]string)
+	for _, v := range classifyItemList {
+		classifyMap[v.Id] = v.ClassifyName
+	}
+
+	item := new(models.Report)
+	item.AddType = req.AddType
+	item.ReportVersion = 2
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = classifyMap[req.ClassifyIdFirst]
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = classifyMap[req.ClassifyIdSecond]
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.State = state
+	item.Content = html.EscapeString(req.Content)
+	item.Stage = maxStage + 1
+	item.ContentSub = html.EscapeString(contentSub)
+	item.CreateTime = req.CreateTime
+	item.ModifyTime = time.Now()
+	item.ReportVersion = req.ReportVersion
+	item.AdminId = sysUser.AdminId
+	item.AdminRealName = sysUser.RealName
+
+	item.ClassifyIdThird = req.ClassifyIdThird
+	item.ClassifyNameThird = classifyMap[req.ClassifyIdThird]
+
+	// 产品要求,如果是多人协作,那么就是章节类型的报告
+	if req.CollaborateType == 2 {
+		item.HasChapter = 1
+		item.ChapterType = ""
+	}
+	item.LastModifyAdminId = sysUser.AdminId
+	item.LastModifyAdminName = sysUser.RealName
+	item.ContentModifyTime = time.Now()
+	item.NeedSplice = 1
+	item.ContentStruct = html.EscapeString(req.ContentStruct)
+	item.HeadImg = req.HeadImg
+	item.EndImg = req.EndImg
+	item.CanvasColor = req.CanvasColor
+	item.HeadResourceId = req.HeadResourceId
+	item.EndResourceId = req.EndResourceId
+	item.CollaborateType = req.CollaborateType
+	item.ReportLayout = req.ReportLayout
+	item.IsPublicPublish = req.IsPublicPublish
+	item.ReportCreateTime = time.Now()
+
+	reportDate := time.Now()
+	t, _ := time.ParseInLocation(utils.FormatDate, req.CreateTime, time.Local)
+	if !t.IsZero() {
+		reportDate = t
+	}
+	err, errMsg := services.AddReportAndChapter(item, req.InheritReportId, req.GrantAdminIdList, reportDate)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	recordItem := &models.ReportStateRecord{
+		ReportId:   item.Id,
+		ReportType: 1,
+		State:      1,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+
+	resp := new(models.AddResp)
+	resp.ReportId = int64(item.Id)
+	resp.ReportCode = item.ReportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// Edit
+// @Title 编辑报告基础信息接口
+// @Description 编辑报告基础信息(不区分报告类型)
+// @Param	request	body models.EditReq true "type json string"
+// @Success 200 {object} models.EditResp
+// @router /edit [post]
+func (this *ReportController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	//if req.Content == "" {
+	//	br.Msg = "报告内容不能为空"
+	//	return
+	//}
+	//更新标记key
+	markStatus, err := services.UpdateReportEditMark(int(req.ReportId), 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	if markStatus.Status == 1 {
+		br.Msg = markStatus.Msg
+		//br.Ret = 202 //202 服务器已接受请求,但尚未处理。
+		return
+	}
+
+	reportInfo, e := models.GetReportByReportId(int(req.ReportId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	if reportInfo.State == models.ReportStatePublished || reportInfo.State == models.ReportStatePass {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		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 {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
+	resp := new(models.EditResp)
+	resp.ReportId = req.ReportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// Detail
+// @Title 获取报告详情接口
+// @Description 获取报告详情
+// @Param	request	body models.ReportDetailReq true "type json string"
+// @Success 200 {object} models.Report
+// @router /detail [get]
+func (this *ReportController) Detail() {
+	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.GetReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	chapterList := make([]*models.ReportChapter, 0)
+	if item.HasChapter == 1 {
+		// 获取章节内容
+		tmpChapterList, err := models.GetPublishedChapterListByReportId(item.Id)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取晨/周报章节列表失败, Err: " + err.Error()
+			return
+		}
+
+		if len(tmpChapterList) > 0 {
+			// 章节类型的字段赋值
+			for _, item := range tmpChapterList {
+				item.Content = html.UnescapeString(item.Content)
+				item.ContentSub = html.UnescapeString(item.ContentSub)
+				chapterList = append(chapterList, item)
+			}
+		}
+
+		//item.Abstract = item.Title
+	}
+	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 {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
+			return
+		}
+		item.HeadImg = headResource.ImgUrl
+		item.HeadStyle = headResource.Style
+	}
+
+	if item.EndResourceId > 0 {
+		endResource, err := smart_report.GetResourceItemById(item.EndResourceId)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
+			return
+		}
+		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)
+		}
+
+	}
+
+	resp := &models.ReportDetailView{
+		ReportDetail: item,
+		ChapterList:  chapterList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// SaveReportContent
+// @Title 保存草稿
+// @Description 保存草稿
+// @Param	request	body models.SaveReportContent true "type json string"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /saveReportContent [post]
+func (this *ReportController) SaveReportContent() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.SaveReportContent
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportId := req.ReportId
+	noChangeFlag := req.NoChange
+
+	if reportId <= 0 {
+		resp := new(models.SaveReportContentResp)
+		resp.ReportId = reportId
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "保存成功"
+		br.Data = resp
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, _ := models.GetReportByReportId(req.ReportId)
+	if reportInfo != nil && reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 标记更新中
+	{
+		markStatus, err := services.UpdateReportEditMark(req.ReportId, 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			br.Msg = markStatus.Msg
+			return
+		}
+	}
+
+	// 内容有过修改的话,那么逻辑处理
+	if noChangeFlag != 1 {
+		content := req.Content
+		if content == "" {
+			content = this.GetString("Content")
+		}
+		content = services.HandleReportContent(content, "del", nil)
+
+		if content != "" {
+			e := utils.ContentXssCheck(req.Content)
+			if e != nil {
+				br.Msg = "存在非法标签"
+				br.ErrMsg = "存在非法标签, Err: " + e.Error()
+				return
+			}
+			contentClean, e := services.FilterReportContentBr(req.Content)
+			if e != nil {
+				br.Msg = "内容去除前后空格失败"
+				br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+				return
+			}
+			content = contentClean
+
+			req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+
+			contentSub, err := services.GetReportContentSub(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.ContentSub = html.EscapeString(contentSub)
+			reportInfo.ContentStruct = html.EscapeString(req.ContentStruct)
+			reportInfo.HeadImg = req.HeadImg
+			reportInfo.EndImg = req.EndImg
+			reportInfo.CanvasColor = req.CanvasColor
+			reportInfo.HeadResourceId = req.HeadResourceId
+			reportInfo.EndResourceId = req.EndResourceId
+			reportInfo.ModifyTime = time.Now()
+			reportInfo.ContentModifyTime = time.Now()
+			updateCols := []string{"Content", "ContentSub", "ContentStruct", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId", "ModifyTime", "ContentModifyTime"}
+			err = reportInfo.UpdateReport(updateCols)
+			if err != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = "保存失败,Err:" + err.Error()
+				return
+			}
+			go models.AddReportSaveLog(reportId, this.SysUser.AdminId, reportInfo.Content, reportInfo.ContentSub, reportInfo.ContentStruct, reportInfo.CanvasColor, this.SysUser.AdminName, reportInfo.HeadResourceId, reportInfo.EndResourceId)
+		}
+	}
+
+	resp := new(models.SaveReportContentResp)
+	resp.ReportId = reportId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// AuthorizedListReport
+// @Title 获取有权限的报告列表接口
+// @Description 获取有权限的报告列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   ClassifyIdFirst   query   int  true       "一级分类id"
+// @Param   ClassifyIdSecond   query   int  true       "二级分类id"
+// @Param   ClassifyIdThird   query   int  true       "三级分类id"
+// @Success 200 {object} models.ReportListResp
+// @router /list/authorized [get]
+func (this *ReportController) AuthorizedListReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("Keyword")
+	classifyIdFirst, _ := this.GetInt("ClassifyIdFirst", 0)
+	classifyIdSecond, _ := this.GetInt("ClassifyIdSecond", 0)
+	classifyIdThird, _ := this.GetInt("ClassifyIdThird", 0)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+	var list []*models.ReportList
+
+	// 没有输入信息,那就不展示
+	if keyword == `` && classifyIdFirst <= 0 {
+		page := paging.GetPaging(currentIndex, pageSize, 0)
+		resp := new(models.ReportListResp)
+		resp.Paging = page
+		resp.List = list
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	// 当前用户有权限的报告id列表
+	grantReportIdList := make([]int, 0)
+	{
+		obj := report.ReportGrant{}
+		grantList, err := obj.GetGrantListByAdminId(this.SysUser.AdminId)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取授权报告id失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range grantList {
+			grantReportIdList = append(grantReportIdList, v.ReportId)
+		}
+	}
+
+	var condition string
+	var pars []interface{}
+
+	if classifyIdFirst > 0 {
+		condition += ` AND a.classify_id_first = ? `
+		pars = append(pars, classifyIdFirst)
+	}
+	if classifyIdSecond > 0 {
+		condition += ` AND a.classify_id_second = ? `
+		pars = append(pars, classifyIdSecond)
+	}
+	if classifyIdThird > 0 {
+		condition += ` AND a.classify_id_third = ? `
+		pars = append(pars, classifyIdThird)
+	}
+
+	if keyword != `` {
+		condition += ` AND a.title LIKE ? `
+		pars = utils.GetLikeKeywordPars(pars, keyword, 1)
+	}
+
+	var err error
+	var total int
+
+	// 如果不是超管,那么只能看到有权限的报告
+	if !utils.IsAdminRole(this.SysUser.RoleTypeCode) {
+		orCondition := `AND ( (a.is_public_publish = ? AND a.state in (2,6)) or a.admin_id = ? `
+		pars = append(pars, 1, this.SysUser.AdminId)
+
+		// 当前用户有权限的报告id列表
+		num := len(grantReportIdList)
+		if num > 0 {
+			orCondition += ` OR a.id in (` + utils.GetOrmInReplace(num) + `)`
+			pars = append(pars, grantReportIdList)
+		}
+		orCondition += ` ) `
+
+		condition += orCondition
+	}
+
+	total, err = models.GetReportListCountByAuthorized(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err = models.GetReportListByAuthorized(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	//for _, item := range list {
+	//	if item.HasChapter == 1 {
+	//		item.CanEdit = true
+	//	} else {
+	//		markStatus, err := services.UpdateReportEditMark(item.Id, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+	//		if err != nil {
+	//			br.Msg = "查询标记状态失败"
+	//			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+	//			return
+	//		}
+	//		if markStatus.Status == 0 {
+	//			item.CanEdit = true
+	//		} else {
+	//			item.Editor = markStatus.Editor
+	//		}
+	//	}
+	//}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ReportListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// BaseDetail
+// @Title 获取报告基础信息详情接口
+// @Description 获取报告基础信息详情接口
+// @Param	request	body models.ReportDetailReq true "type json string"
+// @Success 200 {object} models.Report
+// @router /detail/base [get]
+func (this *ReportController) BaseDetail() {
+	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
+	}
+	reportInfo, err := models.GetReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	// 基础信息就不获取章节信息了
+	chapterList := make([]*models.ReportChapter, 0)
+
+	reportInfo.Content = html.UnescapeString(reportInfo.Content)
+	reportInfo.ContentSub = html.UnescapeString(reportInfo.ContentSub)
+
+	grandAdminList := make([]models.ReportDetailViewAdmin, 0)
+	permissionList := make([]models.ReportDetailViewPermission, 0)
+
+	// 处理报告授权用户列表
+	{
+		obj := report.ReportGrant{}
+		grantList, tmpErr := obj.GetGrantListById(reportId)
+		if tmpErr != nil {
+			br.Msg = "获取章节id授权用户列表失败"
+			br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+			return
+		}
+
+		if len(grantList) > 0 {
+			grandAdminIdList := make([]int, 0)
+			for _, v := range grantList {
+				grandAdminIdList = append(grandAdminIdList, v.AdminId)
+			}
+			adminList, tmpErr := system.GetAdminListByIdList(grandAdminIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id授权用户列表失败"
+				br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+				return
+			}
+			for _, v := range adminList {
+				grandAdminList = append(grandAdminList, models.ReportDetailViewAdmin{
+					AdminId:   v.AdminId,
+					AdminName: v.RealName,
+				})
+			}
+		}
+
+	}
+
+	// 处理章节id关联品种id列表
+	{
+		minClassifyId := reportInfo.ClassifyIdThird
+		if minClassifyId <= 0 {
+			minClassifyId = reportInfo.ClassifyIdSecond
+		}
+		if minClassifyId <= 0 {
+			minClassifyId = reportInfo.ClassifyIdFirst
+		}
+		if minClassifyId <= 0 {
+			br.Msg = "分类异常"
+			br.ErrMsg = "分类异常"
+			return
+		}
+
+		// 获取分类关联的品种id
+		classifyPermissionList, tmpErr := models.GetPermission(minClassifyId)
+		if tmpErr != nil {
+			br.Msg = "获取分类信息失败"
+			br.ErrMsg = "获取失败,Err:" + tmpErr.Error()
+			return
+		}
+
+		if len(classifyPermissionList) > 0 {
+			permissionIdList := make([]int, 0)
+			for _, v := range classifyPermissionList {
+				permissionIdList = append(permissionIdList, v.ChartPermissionId)
+			}
+			adminList, tmpErr := models.GetChartPermissionByIdList(permissionIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id授权用户列表失败"
+				br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+				return
+			}
+			for _, v := range adminList {
+				permissionList = append(permissionList, models.ReportDetailViewPermission{
+					PermissionId:   v.ChartPermissionId,
+					PermissionName: v.PermissionName,
+				})
+			}
+		}
+
+	}
+
+	resp := &models.ReportDetailView{
+		ReportDetail:   reportInfo,
+		ChapterList:    chapterList,
+		GrandAdminList: grandAdminList,
+		PermissionList: permissionList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// EditLayoutImg
+// @Title 版图设置接口
+// @Description 版图设置接口
+// @Param	request	body models.EditLayoutImgReq true "type json string"
+// @Success 200 {object} models.EditResp
+// @router /layout_img/edit [post]
+func (this *ReportController) EditLayoutImg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditLayoutImgReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	//if req.Content == "" {
+	//	br.Msg = "报告内容不能为空"
+	//	return
+	//}
+	//更新标记key
+	markStatus, err := services.UpdateReportEditMark(int(req.ReportId), 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	if markStatus.Status == 1 {
+		br.Msg = markStatus.Msg
+		//br.Ret = 202 //202 服务器已接受请求,但尚未处理。
+		return
+	}
+
+	reportInfo, e := models.GetReportByReportId(int(req.ReportId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	if reportInfo.State == models.ReportStatePublished || reportInfo.State == models.ReportStatePass {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 编辑报告信息
+	err, errMsg := services.EditReportLayoutImg(reportInfo, req, sysUser)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
+	resp := new(models.EditResp)
+	resp.ReportId = req.ReportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// PublishReport
+// @Title 发布报告接口
+// @Description 发布报告
+// @Param	request	body models.PublishReq true "type json string"
+// @Success 200 Ret=200 发布成功
+// @router /publish [post]
+func (this *ReportController) PublishReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportIds := req.ReportIds
+	if reportIds == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+
+	// 这里实际上不会批量发布了...
+	reportArr := strings.Split(reportIds, ",")
+	tips := ""
+	for _, v := range reportArr {
+		vint, err := strconv.Atoi(v)
+		if err != nil {
+			br.Msg = "参数错误"
+			br.ErrMsg = "参数错误,Err:" + err.Error()
+			return
+		}
+
+		// 报告的图表刷新状态校验
+		refreshResult := data.CheckBatchChartRefreshResult("report", vint, 0)
+		if !refreshResult {
+			br.Msg = "图表刷新未完成,请稍后操作"
+			br.ErrMsg = "图表刷新未完成,请稍后操作"
+			br.IsSendEmail = false
+			return
+		}
+
+		// 报告发布
+		tmpTips, err, errMsg := services.PublishReport(vint, req.ReportUrl, this.SysUser)
+		if err != nil {
+			br.Msg = errMsg
+			br.ErrMsg = "报告发布失败,Err:" + err.Error()
+			return
+		}
+		tips = tmpTips
+	}
+	// 发布晨周报部分章节未发布的提示
+	if tips != "" {
+		br.Data = tips
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "发布成功"
+}
+
+// PublishCancelReport
+// @Title 取消发布报告接口
+// @Description 取消发布报告
+// @Param	request	body models.PublishCancelReq true "type json string"
+// @Success 200 Ret=200 取消发布成功
+// @router /publish/cancle [post]
+func (this *ReportController) PublishCancelReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishCancelReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportIds <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+	publishTimeNullFlag := true
+	reportInfo, err := models.GetReportById(req.ReportIds)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+	if reportInfo.MsgIsSend == 1 {
+		publishTimeNullFlag = false
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperateCancelPublish)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
+	err = models.PublishCancelReport(req.ReportIds, state, publishTimeNullFlag, this.SysUser.AdminId, this.SysUser.RealName)
+	if err != nil {
+		br.Msg = "取消发布失败"
+		br.ErrMsg = "取消发布失败,Err:" + err.Error()
+		return
+	}
+	// 更新ES禁用
+	{
+		go services.UpdateReportEs(req.ReportIds, 1)
+	}
+
+	//// 获取审批流设置
+	//confKey := "approval_flow"
+	//confTmp, e := company.GetConfigDetailByCode(confKey)
+	//if e != nil {
+	//	br.Msg = "获取审批流配置失败"
+	//	br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
+	//	return
+	//}
+	//if confTmp.ConfigValue == "1" || confTmp.ConfigValue == "2" || confTmp.ConfigValue == "3" {
+	//	br.Msg = "撤销成功"
+	//} else {
+	//	br.Msg = "取消发布成功"
+	//}
+
+	recordItem := &models.ReportStateRecord{
+		ReportId:   req.ReportIds,
+		ReportType: 1,
+		State:      state,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+	br.Ret = 200
+	br.Success = true
+}
+
+// PrePublishReport
+// @Title 设置定时发布接口
+// @Description 设置定时发布接口
+// @Param	request	body models.PrePublishReq true "type json string"
+// @Success 200 Ret=200 发布成功
+// @router /pre_publish [post]
+func (this *ReportController) PrePublishReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PrePublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId == 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+	if req.PrePublishTime == "" {
+		br.Msg = "发布时间不能为空"
+		return
+	}
+	if req.PreMsgSend != 0 && req.PreMsgSend != 1 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "是否发送模版消息标识错误"
+		return
+	}
+	prePublishTime, err := time.ParseInLocation(utils.FormatDateTime, req.PrePublishTime, time.Local)
+	if err != nil {
+		br.Msg = "发布时间格式错误"
+		br.ErrMsg = "发布时间格式错误,Err:" + err.Error()
+		return
+	}
+	if prePublishTime.Before(time.Now()) {
+		br.Msg = "发布时间不允许选择过去时间"
+		return
+	}
+	if prePublishTime.Before(time.Now().Add(2 * time.Minute)) {
+		br.Msg = "发布时间距离当前时间太近了"
+		return
+	}
+
+	reportDetail, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+	if reportDetail == nil {
+		br.Msg = "报告不存在"
+		return
+	}
+
+	// 如果是章节类型的报告,那么需要确认所有章节已发布
+	if reportDetail.HasChapter == 1 {
+		chapterList, err := models.GetChapterListByReportId(reportId)
+		if err != nil {
+			return
+		}
+		for _, chapter := range chapterList {
+			if chapter.PublishState == 1 {
+				br.Msg = "还存在未发布的章节"
+				br.ErrMsg = "还存在未发布的章节"
+				return
+			}
+		}
+	} else {
+		if reportDetail.Content == "" {
+			br.Msg = "报告内容为空,不可设置定时发布"
+			br.ErrMsg = "报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportDetail.Id)
+			return
+		}
+	}
+
+	if reportDetail.State == 2 {
+		br.Msg = "报告已发布,不可设置定时发布"
+		return
+	}
+
+	// 校验是否开启了审批流
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeChinese, reportDetail.ClassifyIdFirst, reportDetail.ClassifyIdSecond, reportDetail.ClassifyIdThird)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
+		return
+	}
+	if opening {
+		br.Msg = "报告已开启审批流, 不可设置定时发布"
+		return
+	}
+
+	var tmpErr error
+	if tmpErr = models.SetPrePublishReportById(reportDetail.Id, req.PrePublishTime, req.PreMsgSend); tmpErr != nil {
+		br.Msg = "设置定时发布失败"
+		br.ErrMsg = "设置定时发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(reportDetail.Id)
+		return
+	}
+
+	// 生成报告pdf和长图
+	{
+		reportPdfUrl := services.GetGeneralPdfUrl(reportDetail.Id, reportDetail.ReportCode, reportDetail.ReportLayout)
+		go services.Report2pdfAndJpeg(reportPdfUrl, reportDetail.Id, 1)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "定时发布成功"
+}
+
+// SubmitApprove
+// @Title 提交审批
+// @Description 提交审批接口
+// @Param	request	body models.ReportSubmitApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/submit [post]
+func (this *ReportController) SubmitApprove() {
+	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"
+		return
+	}
+	var req models.ReportSubmitApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.Report)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 如果是章节类型的报告,那么需要确认所有章节已发布
+	if reportItem.HasChapter == 1 {
+		chapterList, err := models.GetChapterListByReportId(reportId)
+		if err != nil {
+			return
+		}
+		for _, chapter := range chapterList {
+			if chapter.PublishState == 1 {
+				br.Msg = "还存在未发布的章节"
+				br.ErrMsg = "还存在未发布的章节"
+				return
+			}
+		}
+	} else {
+		if reportItem.Content == "" {
+			br.Msg = "报告内容为空,不可提交"
+			br.ErrMsg = "报告内容为空,不可提交,report_id:" + strconv.Itoa(reportItem.Id)
+			return
+		}
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, reportItem.ClassifyIdThird, models.ReportOperateSubmitApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待审批时, 仅更新状态
+	if state != models.ReportStateWaitApprove {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 提交审批
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeChinese, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, reportItem.ClassifyIdThird, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "提交审批失败, Err: " + e.Error()
+		return
+	}
+	reportItem.ApproveId = approveId
+	reportItem.State = models.ReportStateWaitApprove
+	reportItem.ModifyTime = time.Now().Local()
+	e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 报告权限处理
+	{
+		go services.HandleReportPermission(reportItem)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// CancelApprove
+// @Title 撤销审批
+// @Description 撤销审批
+// @Param	request	body models.ReportCancelApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/cancel [post]
+func (this *ReportController) CancelApprove() {
+	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"
+		return
+	}
+	var req models.ReportCancelApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.Report)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, reportItem.ClassifyIdThird, models.ReportOperateCancelApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待提交时, 仅更新状态
+	if state != models.ReportStateWaitSubmit {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+	//if reportItem.ApproveId <= 0 {
+	//	br.Msg = "报告审批不存在"
+	//	br.ErrMsg = fmt.Sprintf("报告审批不存在, ApproveId: %d", reportItem.ApproveId)
+	//	return
+	//}
+
+	// 撤销审批
+	e = services.CancelReportApprove(report_approve.FlowReportTypeChinese, reportItem.Id, reportItem.ApproveId, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "撤销审批失败, Err: " + e.Error()
+		return
+	}
+	//reportItem.ApproveId = 0
+	//reportItem.State = models.ReportStateWaitSubmit
+	//reportItem.ModifyTime = time.Now().Local()
+	//e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	//if e != nil {
+	//	br.Msg = "操作失败"
+	//	br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+	//	return
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// init
+// @Description: 修复历史报告数据
+// @author: Roc
+// @datetime 2024-06-21 09:19:05
+func init() {
+	//fixApproveRecord()
+	//fixChapterPermission()
+	//fixReportEs()
+	//fixSmartReport()
+}
+
+// 修复研报审批数据
+func fixApproveRecord() {
+	recordOb := new(report_approve.ReportApproveRecord)
+
+	recordCond := fmt.Sprintf(` AND %s = ? AND %s in (?,?)`, report_approve.ReportApproveRecordCols.NodeType, report_approve.ReportApproveRecordCols.State)
+	recordPars := make([]interface{}, 0)
+	recordPars = append(recordPars, 0, report_approve.ReportApproveStatePass, report_approve.ReportApproveStateRefuse)
+	list, e := recordOb.GetItemsByCondition(recordCond, recordPars, []string{}, "")
+	if e != nil {
+		fmt.Println("查找审批记录失败,Err:", e.Error())
+		return
+	}
+	for _, recordItem := range list {
+		//fmt.Println(recordItem)
+		recordItem.NodeState = report_approve.ReportApproveStatePass
+		recordItem.NodeApproveUserId = recordItem.ApproveUserId
+		recordItem.NodeApproveUserName = recordItem.ApproveUserName
+		recordItem.NodeApproveTime = recordItem.ApproveTime
+
+		// 如果不是或签,那么只需要修复自己就好了
+		if recordItem.ApproveType != report_approve.NodeApproveTypeAny {
+			recordCols := []string{"State", "ApproveTime", "ModifyTime", "NodeState", "NodeApproveUserId", "NodeApproveUserName", "NodeApproveTime"}
+			if e = recordItem.Update(recordCols); e != nil {
+				fmt.Println("更新审批记录状态失败,Err:", e.Error())
+			}
+			continue
+		}
+		// 或签
+		// 需要将该审批的同一个节点的记录标记为已审批
+		if e := recordItem.UpdateNodeState(recordItem.ReportApproveId, recordItem.NodeId, recordItem.NodeState, recordItem.NodeApproveUserId, recordItem.NodeApproveUserName, recordItem.NodeApproveTime); e != nil {
+			fmt.Println("更新同一节点的其他审批记录状态失败,Err:", e.Error())
+		}
+	}
+
+	fmt.Println("审批数据修复完成")
+}
+
+// fixChapterPermission
+// @Description: 修复章节关联的品种权限
+// @author: Roc
+// @datetime 2024-06-20 18:08:34
+func fixChapterPermission() {
+	allChapterTypePermissionList, err := models.GetAllChapterTypePermission()
+	if err != nil {
+		fmt.Println("获取所有章节类型ID获取章节类型权限列表失败,Err:", err.Error())
+		return
+	}
+
+	currChapterTypePermissionIdListMap := make(map[int][]int)
+
+	hasPermissionMap := make(map[string]bool)
+	for _, v := range allChapterTypePermissionList {
+		tmpChapterTypePermissionList, ok := currChapterTypePermissionIdListMap[v.ReportChapterTypeId]
+		if !ok {
+			tmpChapterTypePermissionList = make([]int, 0)
+		}
+		key := fmt.Sprint(v.ReportChapterTypeId, "-", v.ChartPermissionId)
+		if _, has := hasPermissionMap[key]; !has {
+			hasPermissionMap[key] = true
+			currChapterTypePermissionIdListMap[v.ReportChapterTypeId] = append(tmpChapterTypePermissionList, v.ChartPermissionId)
+		}
+	}
+
+	notIdList := []int{9675, 9675, 9740, 9749, 9768, 9773, 9791, 9792, 9793, 9850, 9851, 9852, 9852, 9852, 9853, 9854, 9856, 9857, 9857, 9858, 9859, 9860, 9861, 9862, 9862, 9863, 9866}
+	allReportChapterList, err := models.GetAllReportChapter()
+	if err != nil {
+		fmt.Println("获取所有章节失败,Err:", err.Error())
+		return
+	}
+
+	addList := make([]*report.ReportChapterPermissionMapping, 0)
+	for _, v := range allReportChapterList {
+		// 如果是上面的章节id,那么就过滤掉,因为已经入库了
+		if utils.InArrayByInt(notIdList, v.ReportChapterId) {
+			continue
+		}
+		permissionIdList, ok := currChapterTypePermissionIdListMap[v.TypeId]
+		if !ok {
+			continue
+		}
+
+		for _, permissionId := range permissionIdList {
+			addList = append(addList, &report.ReportChapterPermissionMapping{
+				ReportChapterPermissionMappingId: 0,
+				ReportChapterId:                  v.ReportChapterId,
+				ChartPermissionId:                permissionId,
+				CreateTime:                       v.ModifyTime,
+			})
+		}
+
+	}
+
+	obj := report.ReportChapterPermissionMapping{}
+	err = obj.MultiAdd(addList)
+	if err != nil {
+		fmt.Println("批量添加报章节的品种权限失败,Err:", err.Error())
+	}
+
+	return
+}
+
+// fixReportEs
+// @Description: 修复报告es数据
+// @author: Roc
+// @datetime 2024-06-20 18:08:34
+func fixReportEs() {
+
+	//reportInfo, err := models.GetReportByReportId(3941)
+	//if err != nil {
+	//	fmt.Println("查询信息失败,", err)
+	//	return
+	//}
+	//content := utils.TrimHtml(html.UnescapeString(reportInfo.Content))
+	//fmt.Println(content)
+	//
+	//fmt.Println("=========================")
+	//
+	//chapterInfo, err := models.GetReportChapterInfoById(9637)
+	//if err != nil {
+	//	fmt.Println("查询信息失败2,", err)
+	//	return
+	//}
+	//
+	//content = utils.TrimHtml(html.UnescapeString(chapterInfo.Content))
+	//fmt.Println(content)
+	//
+	//services.UpdateReportChapterEs(9637)
+	//return
+
+	var condition string
+	var pars []interface{}
+
+	condition += " AND state in (2,6) "
+	list, err := models.GetReportListV1(condition, pars, 0, 100000)
+	if err != nil {
+		fmt.Println("查询信息失败,", err)
+		return
+	}
+
+	num := len(list)
+	fmt.Println(num, "条待修复报告es数据")
+
+	for k, v := range list {
+		fmt.Println("剩余", num-k, "条")
+		services.UpdateReportEs(v.Id, 2)
+	}
+
+	fmt.Println("报告ES数据修复完成")
+
+	return
+}
+
+// fixSmartReport
+// @Description: 修复智能研报的数据
+// @author: Roc
+// @datetime 2024-06-27 16:54:41
+func fixSmartReport() {
+	fmt.Println("修复智能研报开始")
+	// 先判断是否已经修复过数据,如果修复过,那么就不用修复了
+	{
+		condition := ` AND report_layout=2 and old_report_id>0 `
+		list, err := models.GetReportByCondition(condition, []interface{}{}, []string{}, " order by id asc ", false, 0, 0)
+		if err != nil {
+			fmt.Println("获取已修复的报告列表失败, Err:" + err.Error())
+			return
+		}
+		if len(list) > 0 {
+			fmt.Println("智能研报已经修复过数据,不需要再次修复")
+			return
+		}
+	}
+
+	var condition string
+	var pars []interface{}
+	reportOB := new(smart_report.SmartReport)
+	list, e := reportOB.GetItemsByCondition(condition, pars, []string{}, " smart_report_id asc ")
+	if e != nil {
+		fmt.Println("获取智能报告列表失败, Err:" + e.Error())
+		return
+	}
+
+	addList := make([]*models.Report, 0)
+	for _, v := range list {
+		fmt.Println(v)
+		addList = append(addList, &models.Report{
+			//Id:                  0,
+			AddType:            1,
+			ClassifyIdFirst:    v.ClassifyIdFirst,
+			ClassifyNameFirst:  v.ClassifyNameFirst,
+			ClassifyIdSecond:   v.ClassifyIdSecond,
+			ClassifyNameSecond: v.ClassifyNameSecond,
+			Title:              v.Title,
+			Abstract:           v.Abstract,
+			Author:             v.Author,
+			Frequency:          v.Frequency,
+			CreateTime:         v.CreateTime.Format(utils.FormatDateTime),
+			ModifyTime:         v.ModifyTime,
+			State:              v.State,
+			PublishTime:        v.PublishTime,
+			Stage:              v.Stage,
+			MsgIsSend:          v.MsgIsSend,
+			//ThsMsgIsSend:        v.Tha,
+			Content:             v.Content,
+			VideoUrl:            v.VideoUrl,
+			VideoName:           v.VideoName,
+			VideoPlaySeconds:    fmt.Sprint(v.VideoPlaySeconds),
+			VideoSize:           v.VideoSize,
+			ContentSub:          v.ContentSub,
+			ReportCode:          fmt.Sprint(v.SmartReportId),
+			ReportVersion:       1,
+			HasChapter:          0,
+			ChapterType:         "",
+			OldReportId:         v.SmartReportId,
+			MsgSendTime:         v.MsgSendTime,
+			AdminId:             v.AdminId,
+			AdminRealName:       v.AdminRealName,
+			ApproveTime:         v.ApproveTime,
+			ApproveId:           v.ApproveId,
+			DetailImgUrl:        v.DetailImgUrl,
+			DetailPdfUrl:        v.DetailPdfUrl,
+			ContentStruct:       v.ContentStruct,
+			LastModifyAdminId:   v.LastModifyAdminId,
+			LastModifyAdminName: v.LastModifyAdminName,
+			ContentModifyTime:   v.ContentModifyTime,
+			Pv:                  v.Pv,
+			Uv:                  v.Uv,
+			HeadImg:             v.HeadImg,
+			EndImg:              v.EndImg,
+			CanvasColor:         v.CanvasColor,
+			NeedSplice:          v.NeedSplice,
+			HeadResourceId:      v.HeadResourceId,
+			EndResourceId:       v.EndResourceId,
+			ClassifyIdThird:     0,
+			ClassifyNameThird:   "",
+			CollaborateType:     1,
+			ReportLayout:        2,
+			IsPublicPublish:     1,
+			ReportCreateTime:    v.CreateTime,
+			InheritReportId:     0,
+		})
+	}
+
+	if len(addList) > 0 {
+		err := models.InsertMultiReport(addList)
+		if err != nil {
+			fmt.Println("新增智能研报失败")
+			return
+		}
+
+		reportMap := make(map[int]*models.Report)
+		// 找出已经修复过的新的研报
+		{
+			reportList, tmpErr := models.GetReportByCondition(` AND report_layout=2 and old_report_id>0 `, []interface{}{}, []string{}, " order by id asc ", false, 0, 0)
+			if tmpErr != nil {
+				fmt.Println("获取已修复的报告列表失败, Err:" + tmpErr.Error())
+				return
+			}
+			for _, v := range reportList {
+				v.ReportCode = utils.MD5(strconv.Itoa(v.Id))
+				v.Update([]string{"ReportCode"})
+				reportMap[v.OldReportId] = v
+			}
+		}
+
+		// 找出智能研报的审批单并处理
+		{
+
+			// 智能研报走审批单的
+			reportApproveObj := new(report_approve.ReportApprove)
+
+			approveList, tmpErr := reportApproveObj.GetItemsByCondition(` AND report_type=3 `, []interface{}{}, []string{}, " report_approve_id asc ")
+			if tmpErr != nil {
+				fmt.Println("获取已修复的报告列表失败, Err:" + tmpErr.Error())
+				return
+			}
+
+			for _, v := range approveList {
+				reportInfo, ok := reportMap[v.ReportId]
+				if ok {
+					v.ReportId = reportInfo.Id
+					v.ReportType = 1
+					err = v.Update([]string{"ReportType", "ReportId"})
+					if err != nil {
+						fmt.Println(v.ReportApproveId, "数据修复失败,其对应的报告ID是:", v.ReportId, ";新的报告ID是:", reportInfo.Id)
+					}
+				} else {
+					fmt.Println(v.ReportApproveId, "找不到对应的报告,其对应的报告ID是:", v.ReportId)
+				}
+			}
+		}
+	}
+
+	fmt.Println("修复智能研报完成")
+}

+ 106 - 1
controllers/resource.go

@@ -2,6 +2,7 @@ package controllers
 
 import (
 	"bufio"
+	"encoding/base64"
 	"encoding/json"
 	"eta/eta_mobile/models"
 	"eta/eta_mobile/services"
@@ -10,6 +11,7 @@ import (
 	"fmt"
 	"github.com/kgiannakakis/mp3duration/src/mp3duration"
 	"io"
+	"net/http"
 	"os"
 	"path"
 	"regexp"
@@ -23,6 +25,11 @@ type ResourceController struct {
 	BaseCommonController
 }
 
+// ResourceAuthController 文件资源
+type ResourceAuthController struct {
+	BaseAuthController
+}
+
 // @Title 图片上传
 // @Description 图片上传接口
 // @Param   file   query   file  true       "文件"
@@ -839,7 +846,7 @@ func (this *ResourceController) UploadV2() {
 // @Description 获取STSToken
 // @Success 200 获取成功
 // @router /oss/get_sts_token [get]
-func (this *ResourceController) OssSTSToken() {
+func (this *ResourceAuthController) OssSTSToken() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		this.Data["json"] = br
@@ -919,3 +926,101 @@ func (this *ResourceController) WechatWarning() {
 	br.Ret = 200
 	br.Success = true
 }
+
+// FileDownload
+// @Title 文件下载
+// @Description 文件下载
+// @Param   FileUrl  query  string  true  "文件路径"
+// @Success 200 Ret=200 操作成功
+// @router /file/download [get]
+func (this *ResourceAuthController) FileDownload() {
+	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
+	}
+	//fileName := this.GetString("FileName")
+	//fileName = strings.TrimSpace(fileName)
+	//if fileName == "" {
+	//	br.Msg = "参数有误"
+	//	return
+	//}
+	fileEncode := this.GetString("FileUrl")
+	fileEncode = strings.TrimSpace(fileEncode)
+	if fileEncode == "" {
+		br.Msg = "参数有误"
+		return
+	}
+	fileByte, e := base64.StdEncoding.DecodeString(fileEncode)
+	if e != nil {
+		br.Msg = "下载失败"
+		br.ErrMsg = "文件地址解析失败, Err: " + e.Error()
+		return
+	}
+	fileUrl := string(fileByte)
+	fileArr := strings.Split(fileUrl, "/")
+	if len(fileArr) == 0 {
+		br.Msg = "文件地址有误"
+		return
+	}
+	fileName := fileArr[len(fileArr)-1]
+	//fmt.Println(fileName)
+
+	// 获取文件
+	down, e := http.Get(fileUrl)
+	if e != nil {
+		br.Msg = "下载失败"
+		br.ErrMsg = "文件下载失败, http get: " + e.Error()
+		return
+	}
+	defer down.Body.Close()
+	if down.StatusCode != http.StatusOK {
+		br.Msg = "下载失败"
+		br.ErrMsg = fmt.Sprintf("文件下载失败, http status: %d", down.StatusCode)
+		return
+	}
+
+	// 生成本地文件
+	localFilePath := fmt.Sprintf("%s%s", utils.GetRandStringNoSpecialChar(6), fileName)
+	localFile, e := os.Create(localFilePath)
+	if e != nil {
+		br.Msg = "下载失败"
+		br.ErrMsg = "生成本地文件失败, Err: " + e.Error()
+		return
+	}
+	defer func() {
+		if e = localFile.Close(); e != nil {
+			fmt.Println("local file close err: ", e.Error())
+		}
+		if e = os.Remove(localFilePath); e != nil {
+			fmt.Println("local file remove err: ", e.Error())
+		}
+	}()
+
+	// 写入响应流
+	//_, e = io.Copy(this.Ctx.ResponseWriter, down.Body)
+	_, e = io.Copy(localFile, down.Body)
+	if e != nil {
+		br.Msg = "下载失败"
+		br.ErrMsg = "复制文件资源失败, Err: " + e.Error()
+		return
+	}
+	// 设置响应头
+	//this.Ctx.ResponseWriter.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
+	//this.Ctx.ResponseWriter.Header().Set("Content-Type", "application/octet-stream")
+
+	br.Ret = 200
+	br.Msg = "下载成功"
+	br.Success = true
+	this.Ctx.Output.Download(localFilePath, fileName)
+}

+ 13 - 36
controllers/smart_report/smart_report.go

@@ -79,7 +79,7 @@ func (this *SmartReportController) Add() {
 	stageMax += 1
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, req.ClassifyIdFirst, req.ClassifyIdSecond, 0, models.ReportOperateAdd)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -247,7 +247,7 @@ func (this *SmartReportController) Edit() {
 		contentModify = true
 	}
 	cols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "Title", "Abstract", "Author",
-		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime", "HeadImg", "EndImg", "CanvasColor","HeadResourceId", "EndResourceId"}
+		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId"}
 	item.ClassifyIdFirst = req.ClassifyIdFirst
 	item.ClassifyNameFirst = req.ClassifyNameFirst
 	item.ClassifyIdSecond = req.ClassifyIdSecond
@@ -277,27 +277,6 @@ func (this *SmartReportController) Edit() {
 		return
 	}
 	resp := smart_report.FormatSmartReport2Item(item)
-	if resp.HeadResourceId > 0 {
-		headResource, err := smart_report.GetResourceItemById(resp.HeadResourceId)
-		if err != nil {
-			br.Msg = "操作失败"
-			br.ErrMsg = "获取资源库版头失败, Err: " + e.Error()
-			return
-		}
-		resp.HeadImg = headResource.ImgUrl
-		resp.HeadStyle = headResource.Style
-	}
-
-	if resp.EndResourceId > 0 {
-		endResource, err := smart_report.GetResourceItemById(resp.EndResourceId)
-		if err != nil {
-			br.Msg = "操作失败"
-			br.ErrMsg = "获取资源库版头失败, Err: " + e.Error()
-			return
-		}
-		resp.EndImg = endResource.ImgUrl
-		resp.EndStyle = endResource.Style
-	}
 
 	br.Ret = 200
 	br.Success = true
@@ -414,7 +393,7 @@ func (this *SmartReportController) Detail() {
 		headResource, err := smart_report.GetResourceItemById(resp.HeadResourceId)
 		if err != nil {
 			br.Msg = "操作失败"
-			br.ErrMsg = "获取资源库版头失败, Err: " + e.Error()
+			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
 			return
 		}
 		resp.HeadImg = headResource.ImgUrl
@@ -425,7 +404,7 @@ func (this *SmartReportController) Detail() {
 		endResource, err := smart_report.GetResourceItemById(resp.EndResourceId)
 		if err != nil {
 			br.Msg = "操作失败"
-			br.ErrMsg = "获取资源库版头失败, Err: " + e.Error()
+			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
 			return
 		}
 		resp.EndImg = endResource.ImgUrl
@@ -505,7 +484,7 @@ func (this *SmartReportController) Publish() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, operate)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, 0, operate)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -662,7 +641,7 @@ func (this *SmartReportController) PrePublish() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond)
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, 0)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
@@ -838,7 +817,7 @@ func (this *SmartReportController) SaveContent() {
 	for _, ad := range admins {
 		adminIdName[ad.AdminId] = ad.RealName
 	}
-	editing, e := services.UpdateSmartReportEditing(req.SmartReportId, 1, sysUser.AdminId, sysUser.RealName, adminIdName)
+	editing, e := services.UpdateSmartReportEditing(req.SmartReportId, 1, sysUser.AdminId, sysUser.RealName, adminIdName, this.Lang)
 	if e != nil {
 		br.Msg = e.Error()
 		return
@@ -872,7 +851,7 @@ func (this *SmartReportController) SaveContent() {
 		item.CanvasColor = req.CanvasColor
 		item.HeadResourceId = req.HeadResourceId
 		item.EndResourceId = req.EndResourceId
-		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "HeadImg", "EndImg", "CanvasColor","HeadResourceId", "EndResourceId"}
+		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId"}
 		if e = item.Update(cols); e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "更新报告内容失败"
@@ -949,7 +928,7 @@ func (this *SmartReportController) MarkEditStatus() {
 		adminIdName[ad.AdminId] = ad.RealName
 	}
 
-	data, e := services.UpdateSmartReportEditing(req.SmartReportId, req.Status, sysUser.AdminId, sysUser.RealName, adminIdName)
+	data, e := services.UpdateSmartReportEditing(req.SmartReportId, req.Status, sysUser.AdminId, sysUser.RealName, adminIdName, this.Lang)
 	if e != nil {
 		br.Msg = e.Error()
 		return
@@ -1112,7 +1091,7 @@ func (this *SmartReportController) List() {
 
 	for _, v := range list {
 		item := smart_report.FormatSmartReport2Item(v)
-		mark, e := services.UpdateSmartReportEditing(v.SmartReportId, 2, sysUser.AdminId, sysUser.RealName, adminIdName)
+		mark, e := services.UpdateSmartReportEditing(v.SmartReportId, 2, sysUser.AdminId, sysUser.RealName, adminIdName, this.Lang)
 		if e != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "查询编辑中标记失败, Err:" + e.Error()
@@ -1425,7 +1404,7 @@ func (this *SmartReportController) SubmitApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateSubmitApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1448,7 +1427,7 @@ func (this *SmartReportController) SubmitApprove() {
 	}
 
 	// 提交审批
-	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeSmart, reportItem.SmartReportId, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeSmart, reportItem.SmartReportId, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, sysUser.AdminId, sysUser.RealName)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "提交审批失败, Err: " + e.Error()
@@ -1516,7 +1495,7 @@ func (this *SmartReportController) CancelApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateCancelApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1564,5 +1543,3 @@ func (this *SmartReportController) CancelApprove() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
-
-

+ 374 - 0
controllers/sys_department.go

@@ -0,0 +1,374 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// 系统设置
+type SysDepartmentController struct {
+	BaseAuthController
+}
+
+// @Title 新增部门
+// @Description 新增部门接口
+// @Param	request	body system.SysDepartmentAddReq true "type json string"
+// @Success 200 新增成功
+// @router /department/add [post]
+func (this *SysDepartmentController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req system.SysDepartmentAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.DepartmentName == "" {
+		br.Msg = "部门名称不能为空"
+		return
+	}
+	count, err := system.GetSysDepartmentCount(req.DepartmentName)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败"
+		return
+	}
+	if count > 0 {
+		br.Msg = "部门名称已存在,请重新输入"
+		return
+	}
+	item := new(system.SysDepartment)
+	item.DepartmentName = req.DepartmentName
+	item.CreateTime = time.Now()
+	departmentId, err := system.AddSysDepartment(item)
+	if err != nil {
+		br.Msg = "新增失败"
+		br.ErrMsg = "新增失败,Err:" + err.Error()
+		return
+	}
+
+	// 同步部门缓存
+	if utils.BusinessCode == utils.BusinessCodeRelease {
+		var syncData system.SyncDepartmentData
+		syncData.Source = utils.SOURCE_ETA_FLAG
+		syncData.DepartmentId = int(departmentId)
+		_ = utils.Rc.LPush(utils.CACHE_SYNC_DEPARTMENT, syncData)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "新增成功"
+}
+
+// @Title 修改部门
+// @Description 修改部门接口
+// @Param	request	body system.SysDepartmentEditReq true "type json string"
+// @Success 200 修改成功
+// @router /department/edit [post]
+func (this *SysDepartmentController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req system.SysDepartmentEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.DepartmentId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,DepartmentId 小于等于0 "
+		return
+	}
+	if req.DepartmentName == "" {
+		br.Msg = "部门名称不能为空"
+		return
+	}
+	item, err := system.GetSysDepartmentByName(req.DepartmentName)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		if item.DepartmentId != req.DepartmentId {
+			br.Msg = "名称已存在,请重新输入"
+			return
+		}
+	}
+	err = system.ModifySysDepartment(req.DepartmentName, req.DepartmentId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		return
+	}
+
+	// 同步部门缓存
+	if utils.BusinessCode == utils.BusinessCodeRelease {
+		var syncData system.SyncDepartmentData
+		syncData.Source = utils.SOURCE_ETA_FLAG
+		syncData.DepartmentId = req.DepartmentId
+		_ = utils.Rc.LPush(utils.CACHE_SYNC_DEPARTMENT, syncData)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "修改成功"
+}
+
+// @Title 删除部门
+// @Description 删除部门接口
+// @Param	request	body system.SysDepartmentDeleteReq true "type json string"
+// @Success 200 删除成功
+// @router /department/delete [post]
+func (this *SysDepartmentController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req system.SysDepartmentDeleteReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.DepartmentId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,DepartmentId 小于等于0 "
+		return
+	}
+	err = system.DeleteSysDepartment(req.DepartmentId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 同步部门缓存
+	if utils.BusinessCode == utils.BusinessCodeRelease {
+		var syncData system.SyncDepartmentData
+		syncData.Source = utils.SOURCE_ETA_FLAG
+		syncData.DepartmentId = req.DepartmentId
+		_ = utils.Rc.LPush(utils.CACHE_SYNC_DEPARTMENT, syncData)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// @Title 获取部门列表
+// @Description 获取部门列表接口
+// @Success 200 {object} system.SysDepartmentListResp
+// @router /department/list [get]
+func (this *SysDepartmentController) ListDepartment() {
+	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"
+		return
+	}
+	list, err := system.GetDepartmentList()
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	lenList := len(list)
+	for i := 0; i < lenList; i++ {
+		departmentId := list[i].DepartmentId
+		groupList, err := system.GetSysGroupByDepartmentId(departmentId)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		list[i].Child = groupList
+		list[i].IsDepartment = true
+		for i2, sysGroup := range groupList {
+			teamList, err := system.GetSysTeamByDepartmentId(sysGroup.GroupId)
+			if err != nil {
+				br.Msg = "获取数据失败"
+				br.ErrMsg = "获取数据失败,Err:" + err.Error()
+				return
+			}
+			groupList[i2].Child = teamList
+			groupList[i2].IsGroup = true
+		}
+	}
+	resp := new(system.SysDepartmentListResp)
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// DepartmentUserTree
+// @Title 获取部门及分组用户树
+// @Description 获取部门列表接口
+// @Success 200 {object} system.DepartmentUserTree
+// @router /department/user_tree [get]
+func (this *SysDepartmentController) DepartmentUserTree() {
+	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"
+		return
+	}
+	keywords := this.GetString("Keywords", "")
+	keywords = strings.TrimSpace(keywords)
+
+	// 获取部门/分组/用户
+	departments, e := system.GetDepartmentList()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取部门失败,Err:" + e.Error()
+		return
+	}
+	groups, e := system.GetFullGroup()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取分组失败,Err:" + e.Error()
+		return
+	}
+	cond := ` AND enabled = 1`
+	pars := make([]interface{}, 0)
+	if keywords != "" {
+		kw := fmt.Sprint("%", keywords, "%")
+		cond += ` AND real_name LIKE ?`
+		pars = append(pars, kw)
+	}
+	admins, e := system.GetSysAdminList(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取用户失败,Err:" + e.Error()
+		return
+	}
+
+	// 用户map
+	departmentAdmins := make(map[int][]*system.DepartmentUserTree, 0)
+	groupAdmins := make(map[int][]*system.DepartmentUserTree, 0)
+	for _, v := range admins {
+		t := new(system.DepartmentUserTree)
+		t.NodeId = v.AdminId
+		t.NodeType = 3
+		t.NodeName = v.RealName
+		t.Children = make([]*system.DepartmentUserTree, 0)
+		if groupAdmins[v.GroupId] == nil {
+			groupAdmins[v.GroupId] = make([]*system.DepartmentUserTree, 0)
+		}
+		groupAdmins[v.GroupId] = append(groupAdmins[v.GroupId], t)
+
+		// 直属于部门
+		if v.GroupId == 0 {
+			if departmentAdmins[v.DepartmentId] == nil {
+				departmentAdmins[v.DepartmentId] = make([]*system.DepartmentUserTree, 0)
+			}
+			departmentAdmins[v.DepartmentId] = append(departmentAdmins[v.DepartmentId], t)
+		}
+	}
+
+	// 小组
+	groupTeams := make(map[int][]*system.DepartmentUserTree, 0)
+	for _, v := range groups {
+		if v.ParentId == 0 {
+			continue
+		}
+		// 关键词查询时不显示空组
+		if keywords != "" && groupAdmins[v.GroupId] == nil {
+			continue
+		}
+		t := new(system.DepartmentUserTree)
+		t.NodeId = v.GroupId
+		t.NodeName = v.GroupName
+		t.NodeType = 2
+		t.Children = make([]*system.DepartmentUserTree, 0)
+		t.Children = groupAdmins[v.GroupId]
+		if groupTeams[v.ParentId] == nil {
+			groupTeams[v.ParentId] = make([]*system.DepartmentUserTree, 0)
+		}
+		groupTeams[v.ParentId] = append(groupTeams[v.ParentId], t)
+	}
+
+	// 大组
+	departmentGroups := make(map[int][]*system.DepartmentUserTree, 0)
+	for _, v := range groups {
+		if v.ParentId > 0 {
+			continue
+		}
+		// 关键词查询时不显示空组
+		if keywords != "" && groupAdmins[v.GroupId] == nil && groupTeams[v.GroupId] == nil {
+			continue
+		}
+		t := new(system.DepartmentUserTree)
+		t.NodeId = v.GroupId
+		t.NodeName = v.GroupName
+		t.NodeType = 2
+		t.Children = make([]*system.DepartmentUserTree, 0)
+		if groupTeams[v.GroupId] != nil {
+			t.Children = append(t.Children, groupTeams[v.GroupId]...)
+		}
+		if groupAdmins[v.GroupId] != nil {
+			t.Children = append(t.Children, groupAdmins[v.GroupId]...)
+		}
+		if departmentGroups[v.DepartmentId] == nil {
+			departmentGroups[v.DepartmentId] = make([]*system.DepartmentUserTree, 0)
+		}
+		departmentGroups[v.DepartmentId] = append(departmentGroups[v.DepartmentId], t)
+	}
+
+	// 部门
+	list := make([]*system.DepartmentUserTree, 0)
+	for _, v := range departments {
+		// 关键词查询时不显示空部门
+		if keywords != "" && departmentGroups[v.DepartmentId] == nil && departmentAdmins[v.DepartmentId] == nil {
+			continue
+		}
+		t := new(system.DepartmentUserTree)
+		t.NodeId = v.DepartmentId
+		t.NodeType = 1
+		t.NodeName = v.DepartmentName
+		t.Children = make([]*system.DepartmentUserTree, 0)
+		if departmentGroups[v.DepartmentId] != nil {
+			t.Children = append(t.Children, departmentGroups[v.DepartmentId]...)
+		}
+		if departmentAdmins[v.DepartmentId] != nil {
+			t.Children = append(t.Children, departmentAdmins[v.DepartmentId]...)
+		}
+		list = append(list, t)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}

+ 34 - 1
controllers/user_login.go

@@ -12,6 +12,7 @@ import (
 	"github.com/mojocn/base64Captcha"
 	"image/color"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -490,6 +491,33 @@ func (this *UserLoginController) Login() {
 		}
 		sysUser = emailUser
 	}
+	var wg sync.WaitGroup
+	var FullGroupName string
+	nameChan := make(chan string, 1)
+	//需要对用户的分组展示优化为多级
+	if sysUser.GroupId > 0 {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			groupList, e := system.GetGroupByDepartmentId(sysUser.DepartmentId)
+			if e != nil {
+				nameChan <- ""
+				return
+			}
+			var root = new(system.GroupNode)
+			services.BuildGroupTree(root, groupList, 10, 0)
+			var find bool
+			var fullGroupName string
+			if root != nil {
+				find, fullGroupName = services.GetGroupName(root.Child, sysUser.GroupId)
+			}
+			if find {
+				nameChan <- fullGroupName
+			} else {
+				nameChan <- ""
+			}
+		}()
+	}
 
 	// 登录成功(无论哪种方式登录), 清除异常标记
 	abnormalCache := fmt.Sprint(utils.CACHE_ABNORMAL_LOGIN, sysUser.AdminName)
@@ -587,7 +615,12 @@ func (this *UserLoginController) Login() {
 		record.CreateTime = time.Now()
 		_ = system.AddSysUserLoginRecord(record)
 	}()
-
+	wg.Wait()
+	close(nameChan)
+	FullGroupName = <-nameChan
+	if FullGroupName != "" {
+		resp.GroupName = FullGroupName
+	}
 	br.Data = resp
 	br.Ret = 200
 	br.Success = true

+ 104 - 38
controllers/voice.go

@@ -6,10 +6,7 @@ import (
 	"eta/eta_mobile/services"
 	"eta/eta_mobile/utils"
 	"fmt"
-	"github.com/kgiannakakis/mp3duration/src/mp3duration"
-	"github.com/rdlucklib/rdluck_tools/file"
 	"github.com/rdlucklib/rdluck_tools/http"
-	"io/ioutil"
 	"os"
 	"path"
 	"strconv"
@@ -43,11 +40,11 @@ func (this *VoiceController) Upload() {
 	}
 	reportId, err := this.GetInt("ReportId")
 	if err != nil {
-		br.Msg = "获取资源信息失败"
-		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		br.Msg = "报告id异常"
+		br.ErrMsg = "报告id异常,Err:" + err.Error()
 		return
 	}
-	report, err := models.GetReportItemById(reportId)
+	reportInfo, err := models.GetReportByReportId(reportId)
 	if err != nil {
 		br.Msg = "获取报告信息失败"
 		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
@@ -118,30 +115,55 @@ func (this *VoiceController) Upload() {
 		return
 	}
 
-	var playSeconds float64
-	playSeconds, err = mp3duration.Calculate(fpath)
-	if playSeconds <= 0 {
-		playSeconds, err = utils.GetVideoPlaySeconds(fpath)
-		if err != nil {
-			br.Msg = "获取音频时间失败"
-			br.ErrMsg = "获取音频时间失败,Err:" + err.Error()
-			return
-		}
+	//var playSeconds float64
+	//playSeconds, err = mp3duration.Calculate(fpath)
+	//if playSeconds <= 0 {
+	//	playSeconds, err = utils.GetVideoPlaySeconds(fpath)
+	//	if err != nil {
+	//		br.Msg = "获取音频时间失败"
+	//		br.ErrMsg = "获取音频时间失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
+
+	playSecondsStr, err := utils.GetDuration(fpath) //mp3duration.Calculate(fPath)
+	if err != nil {
+		utils.FileLog.Info("获取音频时间失败,Err:" + err.Error())
 	}
-	createTime := report.CreateTime.Format("0102")
-	videoName := report.Title + "(" + createTime + ")"
 
-	fileBody, err := ioutil.ReadFile(fpath)
+	fileBody, err := os.ReadFile(fpath)
 	videoSize := len(fileBody)
 	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
 	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
 
-	err = models.ModifyReportVideo(reportId, resourceUrl, videoName, sizeStr, playSeconds)
-	if err != nil {
-		br.Msg = "上传失败"
-		br.ErrMsg = "上传失败,Err:" + err.Error()
-		return
+	// 修改报告的音频信息
+	{
+		reportCreateTime, err := time.Parse(utils.FormatDateTime, reportInfo.CreateTime)
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "上传失败,Err:" + err.Error()
+			return
+		}
+
+		createTimeStr := reportCreateTime.Format("0102")
+		videoName := reportInfo.Title + "(" + createTimeStr + ")"
+
+		reportInfo.VideoUrl = resourceUrl
+		reportInfo.VideoName = videoName
+		reportInfo.VideoPlaySeconds = playSecondsStr
+		reportInfo.VideoSize = sizeStr
+		reportInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.VoiceGenerateType = 1
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "LastModifyAdminId", "LastModifyAdminName", "VoiceGenerateType", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告的音频信息失败,Err:" + err.Error()
+			return
+		}
 	}
+
 	resp := new(models.ResourceResp)
 	resp.Id = newId
 	resp.ResourceUrl = resourceUrl
@@ -175,36 +197,80 @@ func (this *VoiceCommonController) Download() {
 		br.ErrMsg = "获取,ReportId,Err:" + err.Error()
 		return
 	}
-	report, err := models.GetReportById(reportId)
+	reportInfo, err := models.GetReportById(reportId)
 	if err != nil {
 		br.Msg = "获取信息失败"
 		br.ErrMsg = "获取信息失败,Err:" + err.Error()
 		return
 	}
-	savePath := time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".mp3"
-	fileBody, err := http.Get(report.VideoUrl)
-	if err != nil {
-		br.Msg = "获取信息失败"
-		br.ErrMsg = "获取信息失败,Err:" + err.Error()
-		return
+
+	savePath, fileName, err, errMsg := services.DownloadVoice(reportInfo)
+	// 如果生成了文件,那么就在退出的时候,删除该文件
+	if savePath != `` {
+		defer func() {
+			os.Remove(savePath)
+		}()
 	}
-	err = file.SaveFile(fileBody, savePath)
+
 	if err != nil {
-		br.Msg = "保存信息失败"
-		br.ErrMsg = "保存信息失败,Err:" + err.Error()
+		br.Msg = errMsg
+		br.ErrMsg = "下载失败,Err:" + err.Error()
 		return
 	}
-	fileName := report.VideoName + ".mp3"
-	this.Ctx.Output.Download(savePath, fileName)
-	defer func() {
-		os.Remove(savePath)
-	}()
+
+	if savePath != `` {
+		this.Ctx.Output.Download(savePath, fileName)
+	}
+
 	br.Ret = 200
 	br.Msg = "下载成功"
 	br.Success = true
+
 	return
 }
 
+//func (this *VoiceCommonController) Download() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	reportId, err := this.GetInt("ReportId")
+//	if err != nil {
+//		br.Msg = "参数错误"
+//		br.ErrMsg = "获取,ReportId,Err:" + err.Error()
+//		return
+//	}
+//	report, err := models.GetReportById(reportId)
+//	if err != nil {
+//		br.Msg = "获取信息失败"
+//		br.ErrMsg = "获取信息失败,Err:" + err.Error()
+//		return
+//	}
+//	savePath := time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".mp3"
+//	fileBody, err := http.Get(report.VideoUrl)
+//	if err != nil {
+//		br.Msg = "获取信息失败"
+//		br.ErrMsg = "获取信息失败,Err:" + err.Error()
+//		return
+//	}
+//	err = file.SaveFile(fileBody, savePath)
+//	if err != nil {
+//		br.Msg = "保存信息失败"
+//		br.ErrMsg = "保存信息失败,Err:" + err.Error()
+//		return
+//	}
+//	fileName := report.VideoName + ".mp3"
+//	this.Ctx.Output.Download(savePath, fileName)
+//	defer func() {
+//		os.Remove(savePath)
+//	}()
+//	br.Ret = 200
+//	br.Msg = "下载成功"
+//	br.Success = true
+//	return
+//}
+
 // ReportChapterDownload
 // @Title 报告章节列表音频下载
 // @Description 音频下载接口

+ 10 - 5
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
 )
 
@@ -53,6 +57,7 @@ require (
 	github.com/aliyun/credentials-go v1.3.1 // indirect
 	github.com/andybalholm/cascadia v1.3.2 // indirect
 	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 // indirect
+	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
@@ -73,6 +78,7 @@ require (
 	github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect
 	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/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
@@ -111,12 +117,11 @@ require (
 	github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
 	github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
-	golang.org/x/crypto v0.19.0 // indirect
+	golang.org/x/crypto v0.24.0 // indirect
 	golang.org/x/image v0.14.0 // indirect
-	golang.org/x/net v0.21.0 // indirect
-	golang.org/x/sync v0.2.0 // indirect
-	golang.org/x/sys v0.17.0 // indirect
-	golang.org/x/text v0.14.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
 	golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect

+ 20 - 9
go.sum

@@ -68,6 +68,8 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoU
 github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
 github.com/aws/aws-sdk-go v1.51.2 h1:Ruwgz5aqIXin5Yfcgc+PCzoqW5tEGb9aDL/JWDsre7k=
 github.com/aws/aws-sdk-go v1.51.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/beego/bee/v2 v2.1.0 h1:4WngbAnkvVOyKy74WXcRH3clon76wkjhuzrV2mx2fQU=
 github.com/beego/bee/v2 v2.1.0/go.mod h1:wDhKy5TNxv46LHKsK2gyxo38ObCOm9PbCN89lWHK3EU=
 github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA=
@@ -222,9 +224,13 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
+github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
+github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
+github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -287,6 +293,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
+github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
 github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
 github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
@@ -404,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=
@@ -493,8 +503,8 @@ 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=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -543,8 +553,8 @@ 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=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -558,8 +568,8 @@ 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=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -593,8 +603,8 @@ 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=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -614,8 +624,9 @@ 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=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

+ 73 - 34
models/business_conf.go

@@ -5,49 +5,62 @@ import (
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"html"
+	"strconv"
 	"strings"
 	"time"
 )
 
-const (
-	BusinessConfUseXf                     = "UseXf"
-	BusinessConfXfAppid                   = "XfAppid"
-	BusinessConfXfApiKey                  = "XfApiKey"
-	BusinessConfXfApiSecret               = "XfApiSecret"
-	BusinessConfXfVcn                     = "XfVcn"
-	BusinessConfEnPptCoverImgs            = "EnPptCoverImgs"
-	BusinessConfIsReportApprove           = "IsReportApprove"
-	BusinessConfReportApproveType         = "ReportApproveType"
-	BusinessConfCompanyName               = "CompanyName"
-	BusinessConfCompanyWatermark          = "CompanyWatermark"
-	BusinessConfWatermarkChart            = "WatermarkChart"
-	BusinessConfLoginSmsTpId              = "LoginSmsTpId"
-	BusinessConfLoginSmsGjTpId            = "LoginSmsGjTpId"
-	BusinessConfSmsJhgnAppKey             = "SmsJhgnAppKey"
-	BusinessConfSmsJhgjAppKey             = "SmsJhgjAppKey"
-	BusinessConfLdapHost                  = "LdapHost"
-	BusinessConfLdapBase                  = "LdapBase"
-	BusinessConfLdapPort                  = "LdapPort"
-	BusinessConfEmailClient               = "EmailClient"
-	BusinessConfEmailServerHost           = "EmailServerHost"
-	BusinessConfEmailServerPort           = "EmailServerPort"
-	BusinessConfEmailSender               = "EmailSender"
-	BusinessConfEmailSenderUserName       = "EmailSenderUserName"
-	BusinessConfEmailSenderPassword       = "EmailSenderPassword"
-	BusinessConfSmsClient                 = "SmsClient"
-	BusinessConfNanHuaSmsAppKey           = "NanHuaSmsAppKey"
-	BusinessConfNanHuaSmsAppSecret        = "NanHuaSmsAppSecret"
-	BusinessConfNanHuaSmsApiHost          = "NanHuaSmsApiHost"
-	BusinessConfLoginSmsTplContent        = "LoginSmsTplContent"
-	BusinessConfLoginEmailTemplateSubject = "LoginEmailTemplateSubject"
-	BusinessConfLoginEmailTemplateContent = "LoginEmailTemplateContent"
-	BusinessConfLdapBindUserSuffix        = "LdapBindUserSuffix"
-	BusinessConfLdapUserFilter            = "LdapUserFilter"
+var (
+	BusinessConfMap map[string]string
+)
 
+const (
+	BusinessConfUseXf                        = "UseXf"
+	BusinessConfXfAppid                      = "XfAppid"
+	BusinessConfXfApiKey                     = "XfApiKey"
+	BusinessConfXfApiSecret                  = "XfApiSecret"
+	BusinessConfXfVcn                        = "XfVcn"
+	BusinessConfEnPptCoverImgs               = "EnPptCoverImgs"
+	BusinessConfIsReportApprove              = "IsReportApprove"
+	BusinessConfReportApproveType            = "ReportApproveType"
+	BusinessConfCompanyName                  = "CompanyName"
+	BusinessConfCompanyWatermark             = "CompanyWatermark"
+	BusinessConfWatermarkChart               = "WatermarkChart"
+	BusinessConfLoginSmsTpId                 = "LoginSmsTpId"
+	BusinessConfLoginSmsGjTpId               = "LoginSmsGjTpId"
+	BusinessConfSmsJhgnAppKey                = "SmsJhgnAppKey"
+	BusinessConfSmsJhgjAppKey                = "SmsJhgjAppKey"
+	BusinessConfLdapHost                     = "LdapHost"
+	BusinessConfLdapBase                     = "LdapBase"
+	BusinessConfLdapPort                     = "LdapPort"
+	BusinessConfEmailClient                  = "EmailClient"
+	BusinessConfEmailServerHost              = "EmailServerHost"
+	BusinessConfEmailServerPort              = "EmailServerPort"
+	BusinessConfEmailSender                  = "EmailSender"
+	BusinessConfEmailSenderUserName          = "EmailSenderUserName"
+	BusinessConfEmailSenderPassword          = "EmailSenderPassword"
+	BusinessConfSmsClient                    = "SmsClient"
+	BusinessConfNanHuaSmsAppKey              = "NanHuaSmsAppKey"
+	BusinessConfNanHuaSmsAppSecret           = "NanHuaSmsAppSecret"
+	BusinessConfNanHuaSmsApiHost             = "NanHuaSmsApiHost"
+	BusinessConfLoginSmsTplContent           = "LoginSmsTplContent"
+	BusinessConfLoginEmailTemplateSubject    = "LoginEmailTemplateSubject"
+	BusinessConfLoginEmailTemplateContent    = "LoginEmailTemplateContent"
+	BusinessConfLdapBindUserSuffix           = "LdapBindUserSuffix"
+	BusinessConfLdapUserFilter               = "LdapUserFilter"
 	BusinessConfTencentApiSecretId           = "TencentApiSecretId"           // 腾讯云API-密钥对
 	BusinessConfTencentApiSecretKey          = "TencentApiSecretKey"          // 腾讯云API-密钥对
 	BusinessConfTencentApiRecTaskCallbackUrl = "TencentApiRecTaskCallbackUrl" // 腾讯云API-语音识别回调地址
 	BusinessConfSmsJhgjVariable              = "SmsJhgjVariable"              // 聚合国际短信变量
+
+	BusinessConfEdbStopRefreshRule = "EdbStopRefreshRule" // 是否停止指标刷新规则
+	BusinessConfReport2ImgUrl      = "Report2ImgUrl"      // 报告转长图地址(用于兼容内外网环境的)
+	BusinessConfReportViewUrl      = "ReportViewUrl"      // 报告详情地址     // 报告详情地址
+
+	BusinessConfEsIndexNameExcel       = "EsIndexNameExcel"       // ES索引名称-表格
+	BusinessConfEsIndexNameDataSource  = "EsIndexNameDataSource"  // 聚合国际短信变量
+	BusinessConfIsOpenChartExpired     = "IsOpenChartExpired"     // 是否开启图表有效期鉴权/报告禁止复制
+	BusinessConfReportChartExpiredTime = "ReportChartExpiredTime" // 图表有效期鉴权时间,单位:分钟
 )
 
 const (
@@ -236,3 +249,29 @@ func InitUseMongoConf() {
 		utils.UseMongo = true
 	}
 }
+
+func InitBusinessConf() {
+	var e error
+	BusinessConfMap, e = GetBusinessConf()
+	if e != nil {
+		return
+	}
+	// ES索引名称
+	if BusinessConfMap[BusinessConfEsIndexNameExcel] != "" {
+		utils.EsExcelIndexName = BusinessConfMap[BusinessConfEsIndexNameExcel]
+	}
+	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
+	}
+}

+ 79 - 0
models/chart_permission.go

@@ -7,6 +7,10 @@ import (
 	"time"
 )
 
+const (
+	FiccProductId = 1
+)
+
 // ChartPermission 报告权限表
 type ChartPermission struct {
 	ChartPermissionId     int       `orm:"column(chart_permission_id);pk" description:"问题ID" json:"chart_permission_id"`
@@ -35,6 +39,18 @@ type ChartPermission struct {
 	IsPublic              int       `description:"是否是公有权限1:公有权限,0私有权限" json:"is_public"`
 }
 
+type ChartPermissionVO struct {
+	ChartPermissionId   int       `orm:"column(chart_permission_id);pk" description:"问题ID" json:"chart_permission_id"`
+	ChartPermissionName string    `description:"名称" json:"chart_permission_name"`
+	PermissionName      string    `description:"权限名" json:"permission_name"`
+	Sort                int       `description:"排序" json:"sort"`
+	Enabled             int       `description:"是否可用" json:"enabled"`
+	CreatedTime         time.Time `description:"创建时间" json:"created_time"`
+	LastUpdatedTime     time.Time `description:"更新时间" json:"last_updated_time"`
+	ParentId            int       `description:"父级权限id" json:"parent_id"`
+	Children            []*ChartPermissionVO
+}
+
 type ChartPermissionItem struct {
 	PermissionId   int    `description:"品种权限ID"`
 	PermissionName string `description:"品种权限名称"`
@@ -210,3 +226,66 @@ func (c *ChartPermission) GetFirstChartPermissionByParentId(parentId int) (item
 	err = o.Raw(sql, parentId).QueryRow(&item)
 	return
 }
+
+// GetChartPermissionById 主键获取品种
+func GetChartPermissionById(permissionId int) (item *ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE chart_permission_id = ?`
+	err = o.Raw(sql, permissionId).QueryRow(&item)
+	return
+}
+
+// GetSecondaryChartPermissions 获取二级权限列表
+func GetSecondaryChartPermissions() (list []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE product_id = ? AND parent_id > 0 AND enabled = 1 ORDER BY parent_id ASC, sort ASC, created_time ASC`
+	_, err = o.Raw(sql, FiccProductId).QueryRows(&list)
+	return
+}
+
+type SimpleChartPermission struct {
+	ChartPermissionId   int                      `description:"品种ID"`
+	ChartPermissionName string                   `description:"品种名称"`
+	Sort                int                      `description:"排序"`
+	Children            []*SimpleChartPermission `description:"子分类"`
+}
+
+func FormatChartPermission2Simple(origin *ChartPermission) (item *SimpleChartPermission) {
+	if origin == nil {
+		return
+	}
+	item = new(SimpleChartPermission)
+	item.ChartPermissionId = origin.ChartPermissionId
+	item.ChartPermissionName = origin.PermissionName
+	item.Sort = origin.Sort
+
+	return
+}
+
+// GetChartPermissionsByProductId 获取权限列表
+func GetChartPermissionsByProductId() (list []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE product_id = ? AND enabled = 1 ORDER BY parent_id ASC, sort ASC, created_time ASC`
+	_, err = o.Raw(sql, FiccProductId).QueryRows(&list)
+
+	return
+}
+
+// GetChartPermissionByIdList
+// @Description: 根据品种ID列表获取权限列表
+// @author: Roc
+// @datetime 2024-06-07 10:32:29
+// @param permissionIdList []int
+// @return items []*ChartPermission
+// @return err error
+func GetChartPermissionByIdList(permissionIdList []int) (items []*ChartPermission, err error) {
+	num := len(permissionIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE chart_permission_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, permissionIdList).QueryRows(&items)
+
+	return
+}

+ 295 - 92
models/classify.go

@@ -40,38 +40,69 @@ type Classify struct {
 	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
 	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
 	IsMassSend        int       `description:"1:群发,0:非群发"`
+	Enabled           int       `description:"是否可用,1可用,0禁用"`
+	Level             int       `description:"层级"`
+	HasChild          int       `description:"是否有子级别,0:下面没有子分类,1:下面有子分类;默认:0"`
+}
+
+type ClassifyVO struct {
+	Id            int    `orm:"column(id);pk"`
+	ClassifyName  string `description:"分类名称"`
+	Sort          int    `json:"-"`
+	ParentId      int    `description:"父级分类id"`
+	ClassifyLabel string `description:"分类标签"`
+	Enabled       int    `description:"是否可用,1可用,0禁用"`
+	Level         int    `description:"层级"`
+	IsCollect     int    `description:"是否收藏 1-是 0-否"`
+	Children      *[]ClassifyVO
 }
 
 type ClassifyAddReq struct {
-	ClassifyName      string                 `description:"分类名称"`
-	ParentId          int                    `description:"父级分类id,没有父级分类传0"`
-	Abstract          string                 `description:"栏目简介"`
-	Descript          string                 `description:"分享描述"`
-	ReportAuthor      string                 `description:"栏目作者"`
-	AuthorDescript    string                 `description:"作者简介"`
-	ColumnImgUrl      string                 `description:"栏目配图"`
-	ReportImgUrl      string                 `description:"报告配图"`
-	HeadImgUrl        string                 `description:"头部banner"`
-	AvatarImgUrl      string                 `description:"头像"`
-	HomeImgUrl        string                 `description:"首页配图"`
-	ClassifyLabel     string                 `description:"分类标签"`
-	ShowType          int                    `description:"展示类型:1-列表 2-专栏"`
-	HasTeleconference int                    `description:"是否有电话会:0-否 1-是"`
-	VipTitle          string                 `description:"研究员头衔"`
-	Sort              int                    `description:"后台排序"`
-	IsShow            int                    `description:"是否在小程序显示:1-显示 0-隐藏"`
-	YbFiccSort        int                    `description:"小程序FICC页排序"`
-	YbFiccIcon        string                 `description:"小程序FICC页icon"`
-	YbFiccPcIcon      string                 `description:"小程序PC端FICC页背景图"`
-	YbIconUrl         string                 `description:"小程序已购页icon"`
-	YbBgUrl           string                 `description:"小程序已购详情背景图"`
-	YbListImg         string                 `description:"小程序研报列表封面图"`
-	YbShareBgImg      string                 `description:"小程序研报详情分享背景图"`
-	YbRightBanner     string                 `description:"Pc端详情页,右侧,报告合集背景图"`
-	MenuList          []*ClassifyMenuSaveReq `description:"子目录列表"`
-	ClassifyMenuId    int                    `description:"二级分类-子目录ID"`
-	RelateTel         int                    `description:"是否在电话会中可选: 0-否; 1-是"`
-	RelateVideo       int                    `description:"是否在路演视频中可选: 0-否; 1-是"`
+	ClassifyName          string `description:"分类名称"`
+	ParentId              int    `description:"父级分类id,没有父级分类传0"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+	/*Abstract              string                 `description:"栏目简介"`
+	Descript              string                 `description:"分享描述"`
+	ReportAuthor          string                 `description:"栏目作者"`
+	AuthorDescript        string                 `description:"作者简介"`
+	ColumnImgUrl          string                 `description:"栏目配图"`
+	ReportImgUrl          string                 `description:"报告配图"`
+	HeadImgUrl            string                 `description:"头部banner"`
+	AvatarImgUrl          string                 `description:"头像"`
+	HomeImgUrl            string                 `description:"首页配图"`
+	ClassifyLabel         string                 `description:"分类标签"`
+	ShowType              int                    `description:"展示类型:1-列表 2-专栏"`
+	HasTeleconference     int                    `description:"是否有电话会:0-否 1-是"`
+	VipTitle              string                 `description:"研究员头衔"`
+	Sort                  int                    `description:"后台排序"`
+	IsShow                int                    `description:"是否在小程序显示:1-显示 0-隐藏"`
+	YbFiccSort            int                    `description:"小程序FICC页排序"`
+	YbFiccIcon            string                 `description:"小程序FICC页icon"`
+	YbFiccPcIcon          string                 `description:"小程序PC端FICC页背景图"`
+	YbIconUrl             string                 `description:"小程序已购页icon"`
+	YbBgUrl               string                 `description:"小程序已购详情背景图"`
+	YbListImg             string                 `description:"小程序研报列表封面图"`
+	YbShareBgImg          string                 `description:"小程序研报详情分享背景图"`
+	YbRightBanner         string                 `description:"Pc端详情页,右侧,报告合集背景图"`
+	MenuList              []*ClassifyMenuSaveReq `description:"子目录列表"`
+	ClassifyMenuId        int                    `description:"二级分类-子目录ID"`
+	RelateTel             int                    `description:"是否在电话会中可选: 0-否; 1-是"`
+	RelateVideo           int                    `description:"是否在路演视频中可选: 0-否; 1-是"`*/
+}
+
+// GetClassifyListByCondition 根据条件查询列表
+func GetClassifyListByCondition(condition string, pars []interface{}) (list []Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from classify where 1 = 1 `
+	sql += condition
+	sql += ` ORDER BY sort ASC, create_time ASC`
+
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	if err != nil {
+		return nil, err
+	}
+
+	return list, err
 }
 
 func GetClassifyByName(classifyName string, parentId int) (item *Classify, err error) {
@@ -141,13 +172,20 @@ func DeleteClassify(classifyId int) (err error) {
 // 修改分类
 func EditClassify(req *EditClassifyReq) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `UPDATE classify SET classify_name = ?,abstract=?, parent_id= ?,descript=?,report_author=?,author_descript=?,column_img_url=?,head_img_url=?,avatar_img_url=?,report_img_url=?,home_img_url=?,classify_label=?,show_type=?,has_teleconference=?,vip_title=?,modify_time= NOW() WHERE id = ? `
-	_, err = o.Raw(sql, req.ClassifyName, req.Abstract, req.ParentId, req.Descript, req.ReportAuthor, req.AuthorDescript, req.ColumnImgUrl, req.HeadImgUrl, req.AvatarImgUrl, req.ReportImgUrl, req.HomeImgUrl, req.ClassifyLabel, req.ShowType, req.HasTeleconference, req.VipTitle, req.ClassifyId).Exec()
+	//sql := `UPDATE classify SET classify_name = ?,abstract=?, parent_id= ?,descript=?,report_author=?,author_descript=?,column_img_url=?,head_img_url=?,avatar_img_url=?,report_img_url=?,home_img_url=?,classify_label=?,show_type=?,has_teleconference=?,vip_title=?,modify_time= NOW() WHERE id = ? `
+	//_, err = o.Raw(sql, req.ClassifyName, req.Abstract, req.ParentId, req.Descript, req.ReportAuthor, req.AuthorDescript, req.ColumnImgUrl, req.HeadImgUrl, req.AvatarImgUrl, req.ReportImgUrl, req.HomeImgUrl, req.ClassifyLabel, req.ShowType, req.HasTeleconference, req.VipTitle, req.ClassifyId).Exec()
+	sql := `UPDATE classify SET classify_name = ?,parent_id= ?,modify_time= NOW() WHERE id = ? `
+	_, err = o.Raw(sql, req.ClassifyName, req.ParentId, req.ClassifyId).Exec()
+
 	return
 }
 
-//获取父级分类
-
+// ParentClassify
+// @Description: 获取父级分类
+// @author: Roc
+// @datetime 2024-06-18 15:03:49
+// @return items []*Classify
+// @return err error
 func ParentClassify() (items []*Classify, err error) {
 	sql := `SELECT * FROM classify WHERE parent_id=0 order by id desc `
 	o := orm.NewOrmUsingDB("rddp")
@@ -164,41 +202,47 @@ func FindByIdClassify(classifyId int) (item *Classify, err error) {
 }
 
 type ClassifyList struct {
-	Id                int       `orm:"column(id);pk"`
-	ClassifyName      string    `description:"分类名称"`
-	Sort              int       `description:"排序"`
-	ParentId          int       `description:"父级分类id"`
-	CreateTime        time.Time `description:"创建时间"`
-	ModifyTime        time.Time `description:"修改时间"`
-	Abstract          string    `description:"简介"`
-	Descript          string    `description:"描述"`
-	ClassifyLabel     string    `description:"分类标签"`
-	ShowType          int       `description:"展示类型:1-列表 2-专栏"`
-	HasTeleconference int       `description:"是否有电话会:0-否 1-是"`
-	IsShow            int       `description:"是否在小程序显示:1-显示 0-隐藏"`
-	YbFiccSort        int       `description:"小程序FICC页排序"`
-	YbFiccIcon        string    `description:"小程序FICC页icon"`
-	YbFiccPcIcon      string    `description:"小程序PC端FICC页背景图"`
-	YbIconUrl         string    `description:"小程序已购页icon"`
-	YbBgUrl           string    `description:"小程序已购详情背景图"`
-	YbListImg         string    `description:"小程序研报列表封面图"`
-	YbShareBgImg      string    `description:"小程序研报详情分享背景图"`
-	YbRightBanner     string    `description:"Pc端详情页,右侧,报告合集背景图"`
-	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
-	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
-	Child             []*ClassifyItem
-	ClassifyMenuList  []*ClassifyMenu
+	Id                    int       `orm:"column(id);pk"`
+	ClassifyName          string    `description:"分类名称"`
+	Sort                  int       `description:"排序"`
+	ParentId              int       `description:"父级分类id"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+	Abstract              string    `description:"简介"`
+	Descript              string    `description:"描述"`
+	ClassifyLabel         string    `description:"分类标签"`
+	ShowType              int       `description:"展示类型:1-列表 2-专栏"`
+	HasTeleconference     int       `description:"是否有电话会:0-否 1-是"`
+	IsShow                int       `description:"是否在小程序显示:1-显示 0-隐藏"`
+	YbFiccSort            int       `description:"小程序FICC页排序"`
+	YbFiccIcon            string    `description:"小程序FICC页icon"`
+	YbFiccPcIcon          string    `description:"小程序PC端FICC页背景图"`
+	YbIconUrl             string    `description:"小程序已购页icon"`
+	YbBgUrl               string    `description:"小程序已购详情背景图"`
+	YbListImg             string    `description:"小程序研报列表封面图"`
+	YbShareBgImg          string    `description:"小程序研报详情分享背景图"`
+	YbRightBanner         string    `description:"Pc端详情页,右侧,报告合集背景图"`
+	RelateTel             int       `description:"是否在电话会中可选: 0-否; 1-是"`
+	RelateVideo           int       `description:"是否在路演视频中可选: 0-否; 1-是"`
+	Enabled               int       `description:"是否可用,1可用,0禁用"`
+	Child                 []*ClassifyList
+	ClassifyMenuId        int `description:"二级分类-子目录ID"`
+	ClassifyMenuList      []*ClassifyMenu
+	ChartPermissionIdList []int `description:"绑定的权限ID"`
+	Level                 int   `description:"层级"`
+	HasChild              int   `description:"是否有子级别,0:下面没有子分类,1:下面有子分类;默认:0"`
 }
 
 type ClassifyItem struct {
 	Classify
-	ClassifyMenuId   int `description:"二级分类-子目录ID"`
-	ClassifyMenuList []*ClassifyMenu
+	ClassifyMenuId        int `description:"二级分类-子目录ID"`
+	ClassifyMenuList      []*ClassifyMenu
+	ChartPermissionIdList []int `description:"绑定的权限ID"`
+	Child                 []*ClassifyItem
 }
 
 type ClassifyListResp struct {
-	List   []*ClassifyList
-	Paging *paging.PagingItem `description:"分页数据"`
+	List []*ClassifyList
 }
 
 type ClassifyPermissionListResp struct {
@@ -207,14 +251,9 @@ type ClassifyPermissionListResp struct {
 }
 
 // 获取分类列表
-func GetClassifyList(keyWord, companyType string, hideDayWeek, enabled int) (items []*ClassifyList, err error) {
+func GetClassifyList(keyWord string, enabled int) (items []*ClassifyList, err error) {
 	sql := ``
 	companyTypeSqlStr := ``
-	if companyType == "ficc" {
-		companyTypeSqlStr = " AND id != 40 AND parent_id != 40 "
-	} else if companyType == "权益" {
-		companyTypeSqlStr = " AND (id = 40 or parent_id = 40)  "
-	}
 	if enabled == 1 {
 		companyTypeSqlStr += ` AND enabled = 1 `
 	}
@@ -232,9 +271,6 @@ func GetClassifyList(keyWord, companyType string, hideDayWeek, enabled int) (ite
 		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
 	} else {
 		sql = `SELECT * FROM classify WHERE parent_id=0 ` + companyTypeSqlStr
-		if hideDayWeek == 1 {
-			sql += ` AND classify_name <> '晨报' AND classify_name <> '周报' `
-		}
 
 		sql += ` ORDER BY sort ASC, create_time ASC`
 	}
@@ -254,16 +290,19 @@ func GetClassifyListCount(keyWord, companyType string, hideDayWeek int) (count i
 	} else if companyType == "权益" {
 		companyTypeSqlStr = " AND (id = 40 or parent_id = 40)  "
 	}
+
+	pars := make([]interface{}, 0)
+
 	if keyWord != "" {
 		sqlCount = `SELECT  COUNT(1) AS count FROM (
                SELECT * FROM classify
-               WHERE parent_id=0 ` + companyTypeSqlStr + `  AND classify_name LIKE '%` + keyWord + `%'
+               WHERE parent_id=0 ` + companyTypeSqlStr + `  AND classify_name LIKE ?
                UNION
                SELECT * FROM classify
                WHERE id IN(SELECT parent_id FROM classify
-               WHERE parent_id>0 ` + companyTypeSqlStr + `  AND classify_name LIKE '%` + keyWord + `%')
+               WHERE parent_id>0 ` + companyTypeSqlStr + `  AND classify_name LIKE ? )
                )AS t `
-
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
 	} else {
 		sqlCount = `SELECT COUNT(1) AS count FROM classify WHERE parent_id=0 ` + companyTypeSqlStr
 		if hideDayWeek == 1 {
@@ -271,7 +310,7 @@ func GetClassifyListCount(keyWord, companyType string, hideDayWeek int) (count i
 		}
 	}
 	o := orm.NewOrmUsingDB("rddp")
-	err = o.Raw(sqlCount).QueryRow(&count)
+	err = o.Raw(sqlCount, pars...).QueryRow(&count)
 	return
 }
 
@@ -300,12 +339,16 @@ type FindByIdClassifyReq struct {
 func GetClassifyChild(parentId int, keyWord string) (items []*Classify, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := ``
+	pars := make([]interface{}, 0)
 	if keyWord != "" {
-		sql = `SELECT * FROM classify WHERE parent_id=? AND classify_name LIKE '%` + keyWord + `%' ORDER BY create_time ASC `
+		sql = `SELECT * FROM classify WHERE classify_name LIKE ? AND parent_id=? ORDER BY create_time ASC `
+		pars = append(pars, utils.GetLikeKeyword(keyWord))
 	} else {
 		sql = `SELECT * FROM classify WHERE parent_id=? ORDER BY create_time ASC `
 	}
-	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	pars = append(pars, parentId)
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+
 	return
 }
 
@@ -404,23 +447,183 @@ type RelateTelSecClassifyWithPermissions struct {
 	ChartPermissionIds string `description:"权限IDs"`
 }
 
-// GetRelateTelSecClassifyAndChartPermissions 获取关联了电话会的二级分类及权限
-func GetRelateTelSecClassifyAndChartPermissions() (items []*RelateTelSecClassifyWithPermissions, err error) {
-	dbName := `weekly_report`
-	if utils.RunMode == "debug" {
-		dbName = `test_v2_weekly_report`
+// UpdateClassifySortByParentId 根据父类id更新排序
+func UpdateClassifySortByParentId(parentId, permissionId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update classify set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? `
+	if permissionId > 0 {
+		sql += ` or ( id > ` + fmt.Sprint(permissionId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
 	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetMaxSortByParentId 获取最大的排序值
+func (classifyInfo *Classify) GetMaxSortByParentId(parentId int) (maxSort int, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT
-				a.id,
-				a.classify_name,
-				GROUP_CONCAT(b.chart_permission_id) AS chart_permission_ids
-			FROM classify AS a
-			LEFT JOIN %s.chart_permission_search_key_word_mapping AS b ON b.from = 'rddp' AND a.classify_name = b.key_word
-			WHERE
-				a.parent_id > 0 AND a.relate_tel = 1
-			GROUP BY a.id`
-	sql = fmt.Sprintf(sql, dbName)
-	_, err = o.Raw(sql).QueryRows(&items)
+	sql := `SELECT max(sort) AS sort FROM classify WHERE parent_id = ? `
+	err = o.Raw(sql, parentId).QueryRow(&maxSort)
 	return
 }
+
+// GetMaxSort 获取最大的排序值
+func (classifyInfo *Classify) GetMaxSort() (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM classify`
+	err = o.Raw(sql).QueryRow(&maxSort)
+	return
+}
+
+// GetFirstClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (classifyInfo *Classify) GetFirstClassifyByParentId(parentId int) (item *Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM classify WHERE parent_id = ? order by sort asc, id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+type ClassifyMoveReq struct {
+	ClassifyId     int `description:"分类ID"`
+	PrevClassifyId int `description:"上一个兄弟节点分类id"`
+	NextClassifyId int `description:"下一个兄弟节点分类id"`
+}
+
+type ClassifySetEnabledReq struct {
+	ClassifyId int `description:"分类ID"`
+	Enabled    int `description:"是否可用,1可用,0禁用"`
+}
+
+func (classifyInfo *Classify) SetEnabled(id, enabled int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := ` UPDATE classify SET enabled =?  WHERE id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` UPDATE classify SET enabled =?  WHERE parent_id = ?`
+	_, err = to.Raw(sql, enabled, id).Exec()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// GetCountClassifyChildByParentId
+// @Description: 获取父级分类下子分类数量
+// @author: Roc
+// @datetime 2024-06-17 10:58:46
+// @param parentId int
+// @return total int
+// @return err error
+func GetCountClassifyChildByParentId(parentId int) (total int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT count(1) AS total FROM classify WHERE parent_id = ? `
+	err = o.Raw(sql, parentId).QueryRow(&total)
+
+	return
+}
+
+// GetClassifyListByKeyword
+// @Description: 获取分类列表
+// @author: Roc
+// @datetime 2024-06-19 09:49:33
+// @param keyWord string
+// @param enabled int
+// @return items []*ClassifyList
+// @return err error
+func GetClassifyListByKeyword(keyWord string, enabled int) (items []*ClassifyList, err error) {
+	sql := ``
+	pars := make([]interface{}, 0)
+
+	sql = `SELECT * FROM classify WHERE 1=1 `
+	if enabled == 1 {
+		sql += ` AND enabled = 1 `
+	}
+
+	if keyWord != `` {
+		sql += ` AND classify_name LIKE ? `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 1)
+	}
+	sql += ` ORDER BY sort ASC, create_time ASC`
+
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+// GetClassifyListByParentIdList
+// @Description: 获取分类列表
+// @author: Roc
+// @datetime 2024-06-19 09:49:33
+// @param keyWord string
+// @param enabled int
+// @return items []*ClassifyList
+// @return err error
+func GetClassifyListByParentIdList(parentClassifyIdList []int) (items []*ClassifyList, err error) {
+	num := len(parentClassifyIdList)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT * FROM classify WHERE id in (` + utils.GetOrmInReplace(num) + `) ORDER BY sort ASC, create_time ASC`
+
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, parentClassifyIdList).QueryRows(&items)
+	return
+}
+
+// GetClassifyListByIdList
+// @Description: 根据指标ID列表,获取分类列表
+// @author: Roc
+// @datetime 2024-06-27 15:23:57
+// @param classifyIdList []int
+// @return items []*Classify
+// @return err error
+func GetClassifyListByIdList(classifyIdList []int) (items []*Classify, err error) {
+	num := len(classifyIdList)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT * FROM classify WHERE id IN (` + utils.GetOrmInReplace(num) + `) `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, classifyIdList).QueryRows(&items)
+	return
+}
+
+// GetClassifyListByIdStrList
+// @Description: 根据指标ID列表,获取分类列表
+// @author: Roc
+// @datetime 2024-06-27 15:23:57
+// @param classifyIdList []int
+// @return items []*Classify
+// @return err error
+func GetClassifyListByIdStrList(classifyIdStrList []string) (items []*Classify, err error) {
+	num := len(classifyIdStrList)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT * FROM classify WHERE id IN (` + utils.GetOrmInReplace(num) + `) `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, classifyIdStrList).QueryRows(&items)
+	return
+}
+
+// GetClassifyListByParentId 查询父分类下所有的子分类
+func GetClassifyListByParentId(parentId int) (items []*Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM classify WHERE parent_id = ? and enabled = 1`
+
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+
+	return items, err
+}

+ 46 - 0
models/company/company_config.go

@@ -1,6 +1,9 @@
 package company
 
 import (
+	"encoding/json"
+	"errors"
+	"eta/eta_mobile/utils"
 	"github.com/beego/beego/v2/client/orm"
 )
 
@@ -33,3 +36,46 @@ func GetConfigDetailByCode(configCode string) (item CrmConfig, err error) {
 	err = o.Raw(sql, configCode).QueryRow(&item)
 	return
 }
+
+// ConfigClassifyId
+// @Description: 后台配置的报告id
+type ConfigClassifyId struct {
+	Debug   int `json:"debug"`
+	Release int `json:"release"`
+}
+
+// GetReportClassifyIdByConfigKey
+// @Description: 获取关联的报告id
+// @author: Roc
+// @datetime 2024-06-18 14:10:27
+// @param configKey string
+// @return classifyId int
+// @return err error
+func GetReportClassifyIdByConfigKey(configKey string) (classifyId int, err error) {
+	// 别问为啥要从配置里拿=_=!
+	conf, e := GetConfigDetailByCode(configKey)
+	if e != nil {
+		err = errors.New("获取配置的id失败, Err: " + e.Error())
+		return
+	}
+	if conf.ConfigValue == "" {
+		err = errors.New("ID配置有误")
+		return
+	}
+	type TwoWeekIdConf struct {
+		Debug   []int
+		Release []int
+	}
+	classifyIdConf := new(ConfigClassifyId)
+	if e = json.Unmarshal([]byte(conf.ConfigValue), &classifyIdConf); e != nil {
+		err = errors.New("解析ID配置失败, Err: " + e.Error())
+		return
+	}
+	if utils.RunMode == "debug" {
+		classifyId = classifyIdConf.Debug
+	} else {
+		classifyId = classifyIdConf.Release
+	}
+
+	return
+}

+ 25 - 0
models/data_manage/chart_classify.go

@@ -424,3 +424,28 @@ func GetChartClassifyBySourceAndIsJoinPermission(source, isJoinPermission int) (
 	_, err = o.Raw(sql, source, isJoinPermission).QueryRows(&items)
 	return
 }
+
+// GetChartClassifyAllBySource 根据来源获取所有分类
+func GetChartClassifyAllBySource(source int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE source = ? ORDER BY parent_id ASC, sort ASC, chart_classify_id ASC`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+
+// GetChartInfoBySourceAndParentId 根据图表来源及父级ID获取图表
+func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT chart_info_id,chart_classify_id,chart_name AS chart_classify_name,chart_name_en AS chart_classify_name_en,
+             unique_code,sys_user_id,sys_user_real_name,date_type,start_date,end_date,chart_type,calendar,season_start_date,season_end_date,source
+            FROM chart_info WHERE source = ? AND chart_classify_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, source, parentId)
+	if adminId > 0 {
+		sql += ` AND sys_user_id = ?`
+		pars = append(pars, adminId)
+	}
+	sql += ` ORDER BY sort asc,chart_info_id ASC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}

+ 36 - 7
models/data_manage/chart_info.go

@@ -61,8 +61,17 @@ type ChartInfo struct {
 
 type ChartInfoMore struct {
 	ChartInfo
-	IsEnChart     bool `description:"是否展示英文标识"`
-	HaveOperaAuth bool `description:"是否有数据权限,默认:false"`
+	IsEnChart     bool   `description:"是否展示英文标识"`
+	HaveOperaAuth bool   `description:"是否有数据权限,默认:false"`
+	SearchText    string `description:"搜索结果(含高亮)"`
+}
+
+// AreaExtraConf 面积图配置
+type AreaExtraConf struct {
+	IsHeap            int `description:"是否堆积 1-堆积 2-不堆积"`
+	HeapWay           int `description:"堆积方式 1-普通 2-百分比"`
+	StandardEdbInfoId int `description:"基准指标id"`
+	NullDealWay       int `description:"空值处理方式,1-插值填充 2-前值填充 3-后值填充 4-等于0 5-删除日期"`
 }
 
 func AddChartInfo(item *ChartInfo) (lastId int64, err error) {
@@ -187,6 +196,7 @@ type ChartSaveItem struct {
 	ChartColor        string  `description:"颜色"`
 	PredictChartColor string  `description:"预测数据的颜色"`
 	ChartWidth        float64 `description:"线条大小"`
+	ChartScale        float64 `description:"参考刻度线"`
 	Source            int     `description:"1:ETA图库;2:商品价格曲线"`
 	EdbAliasName      string  `description:"中文别名"`
 	IsConvert         int     `description:"是否数据转换 0不转 1转"`
@@ -643,6 +653,7 @@ type ChartEdbInfoMapping struct {
 	ChartColor          string  `description:"颜色"`
 	PredictChartColor   string  `description:"预测数据的颜色"`
 	ChartWidth          float64 `description:"线条大小"`
+	ChartScale          float64 `description:"参考刻度线"`
 	ChartType           int     `description:"生成样式:1:曲线图,2:季节性图,3:面积图,4:柱状图,5:散点图,6:组合图,7:柱方图,8:商品价格曲线图,9:相关性图"`
 	LatestDate          string  `description:"数据最新日期"`
 	LatestValue         float64 `description:"数据最新值"`
@@ -1502,7 +1513,7 @@ func ChartInfoSearchByKeyWord(keyword string, showSysId int) (searchList []*Char
 }
 
 // ChartInfoSearchByEmptyKeyWord 没有关键字的时候获取默认100条数据
-func ChartInfoSearchByEmptyKeyWord(showSysId int, sourceList []int, noPermissionChartIdList []int, startSize, pageSize int) (total int64, searchList []*ChartInfo, err error) {
+func ChartInfoSearchByEmptyKeyWord(showSysId int, sourceList []int, noPermissionChartIdList []int, startSize, pageSize int) (total int64, searchList []*ChartInfoMore, err error) {
 	num := len(sourceList)
 	o := orm.NewOrmUsingDB("data")
 
@@ -1935,10 +1946,12 @@ func ModifyChartInfoUserIdByOldUserId(oldUserIdList []int, userId int, userName
 }
 
 type FutureGoodBarChartInfoReq struct {
-	EdbInfoIdList []BarChartInfoEdbItemReq `description:"指标信息"`
-	DateList      []BarChartInfoDateReq    `description:"日期配置"`
-	XDataList     []XData                  `description:"横轴配置"`
-	BaseEdbInfoId int                      `description:"日期基准指标id"`
+	EdbInfoIdList       []BarChartInfoEdbItemReq `description:"指标信息"`
+	DateList            []BarChartInfoDateReq    `description:"日期配置"`
+	XDataList           []XData                  `description:"横轴配置"`
+	BaseEdbInfoId       int                      `description:"日期基准指标id"`
+	FutureGoodEdbName   string                   `description:"期货名称"`
+	FutureGoodEdbNameEn string                   `description:"期货英文名称"`
 }
 
 // BarChartInfoReq 柱方图预览请求数据
@@ -2737,3 +2750,19 @@ func getThsHfEdbDataListMinAndMaxByMongo(source, subSource, edbInfoId int, start
 	maxData = result.MaxValue
 	return
 }
+
+type BalanceTableChartListResp struct {
+	List []*BalanceChartInfoDetailResp
+}
+
+type BalanceChartInfoDetailResp struct {
+	*ChartInfoDetailResp
+	ExcelEdbList []*ExcelChartEdbView
+}
+
+type ExcelChartEdbView struct {
+	ExcelChartEdbId int
+	DateSequenceStr string `description:"日期序列选区"`
+	DataSequenceStr string `description:"数据序列选区"`
+	FromTag         string `description:"标签"`
+}

+ 438 - 0
models/data_manage/chart_info_range_analysis.go

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

+ 33 - 0
models/data_manage/data_manage_permission/excel.go

@@ -971,3 +971,36 @@ func GetExcelInfoDataPermissionClassifyNoAuthRecordListByUserId(userId, excelSou
 
 	return
 }
+
+// ExcelInfoPermissionAdminAuth 含创建人的表格权限
+type ExcelInfoPermissionAdminAuth struct {
+	ExcelInfoPermission
+	ExcelName    string `json:"excel_name"`     // 表格名称
+	UniqueCode   string `json:"unique_code"`    // 唯一编码
+	CreateUserId int    `json:"create_user_id"` // 创建人ID
+}
+
+// GetAdminAuthExcelInfoPermission 获取用户有权限的表格
+func GetAdminAuthExcelInfoPermission(source, adminId int, keywords string) (items []*ExcelInfoPermissionAdminAuth, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.*, b.sys_user_id AS create_user_id, b.excel_name, b.unique_code FROM excel_info_permission AS a
+		JOIN excel_info AS b ON a.excel_info_id = b.excel_info_id
+		WHERE a.source = ? AND (b.sys_user_id = ? OR a.sys_user_id = ?)`
+	var pars []interface{}
+	pars = append(pars, source, adminId, adminId)
+	if keywords != "" {
+		sql += ` AND b.excel_name LIKE ?`
+		pars = append(pars, keywords)
+	}
+	sql += ` ORDER BY a.create_time ASC`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetExcelInfoDataNoPermissionByUserId 获取用户所有无权限表格
+func GetExcelInfoDataNoPermissionByUserId(userId, source int) (items []*DataPermissionNoAuthRecord, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT excel_info_permission_no_auth_record_id as data_permission_no_auth_record_id,op_unique_code,source as sub_source,excel_info_id as data_id,excel_name as data_name,sys_user_id,create_time FROM excel_info_permission_no_auth_record WHERE sys_user_id = ? AND source = ? ORDER BY excel_info_permission_no_auth_record_id desc`
+	_, err = o.Raw(sql, userId, source).QueryRows(&items)
+	return
+}

+ 2 - 0
models/data_manage/edb_data_base.go

@@ -173,6 +173,8 @@ func GetEdbDataTableName(source, subSource int) (tableName string) {
 		tableName = "edb_data_gz"
 	case utils.DATA_SOURCE_ICPI: //ICPI消费价格指数->79
 		tableName = "edb_data_icpi"
+	case utils.DATA_SOURCE_PREDICT: // 基础预测指标->30
+		tableName = "edb_data_predict_base"
 	default:
 		edbSource := EdbSourceIdMap[source]
 		if edbSource != nil {

+ 1 - 0
models/data_manage/edb_info.go

@@ -402,6 +402,7 @@ type EdbInfoList struct {
 	NoUpdate         int8                    `description:"是否停止更新,0:继续更新;1:停止更新"`
 	IsJoinPermission int                     `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth    bool                    `description:"是否有数据权限,默认:false"`
+	SearchText       string                  `description:"搜索结果(含高亮)"`
 }
 
 type EdbDataInsertConfigItem struct {

+ 107 - 15
models/data_manage/excel/excel_info.go

@@ -32,6 +32,7 @@ type ExcelInfo struct {
 	RelExcelInfoId     int       `description:"平衡表里静态表关联的动态表excel id"`
 	VersionName        string    `description:"静态表版本名称"`
 	SourcesFrom        string    `description:"图表来源"`
+	ExtraConfig        string    `description:"额外配置"`
 }
 
 // Update 更新 excel表格基础信息
@@ -42,21 +43,24 @@ func (excelInfo *ExcelInfo) Update(cols []string) (err error) {
 }
 
 type MyExcelInfoList struct {
-	ExcelInfoId      int       `orm:"column(excel_info_id);pk"`
-	Source           int       `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
-	ExcelType        int       `description:"表格类型,1:指标列,2:日期列,默认:1"`
-	ExcelName        string    `description:"表格名称"`
-	UniqueCode       string    `description:"表格唯一编码"`
-	ExcelClassifyId  int       `description:"表格分类id"`
-	SysUserId        int       `description:"操作人id"`
-	SysUserRealName  string    `description:"操作人真实姓名"`
-	ExcelImage       string    `description:"表格图片"`
-	FileUrl          string    `description:"表格下载地址"`
-	Sort             int       `description:"排序字段,数字越小越排前面"`
-	ModifyTime       time.Time `description:"最近修改日期"`
-	CreateTime       time.Time `description:"创建日期"`
-	IsJoinPermission int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
-	HaveOperaAuth    bool      `description:"是否有数据权限"`
+	ExcelInfoId      int                   `orm:"column(excel_info_id);pk"`
+	Source           int                   `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
+	ExcelType        int                   `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName        string                `description:"表格名称"`
+	UniqueCode       string                `description:"表格唯一编码"`
+	ExcelClassifyId  int                   `description:"表格分类id"`
+	SysUserId        int                   `description:"操作人id"`
+	SysUserRealName  string                `description:"操作人真实姓名"`
+	ExcelImage       string                `description:"表格图片"`
+	FileUrl          string                `description:"表格下载地址"`
+	Sort             int                   `description:"排序字段,数字越小越排前面"`
+	ModifyTime       time.Time             `description:"最近修改日期"`
+	CreateTime       time.Time             `description:"创建日期"`
+	IsJoinPermission int                   `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	HaveOperaAuth    bool                  `description:"是否有数据权限"`
+	Button           ExcelInfoDetailButton `description:"操作权限"`
+	CanEdit          bool                  `description:"是否可编辑"`
+	Editor           string                `description:"编辑人"`
 }
 
 // AddExcelInfo 新增表格
@@ -653,3 +657,91 @@ type ExcelInfoDetailButton struct {
 	RefreshEdbButton bool `description:"是否可刷新指标"`
 	OpWorkerButton   bool `description:"是否修改协作人"`
 }
+
+// GetChildExcelInfoByParentId 根据id 获取eta表格详情
+func GetChildExcelInfoByParentId(parentId int) (items []*ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT excel_info_id,source,excel_type,excel_name,unique_code,excel_classify_id,sys_user_id,sys_user_real_name,excel_image,file_url,sort,create_time,modify_time,is_join_permission, parent_id, balance_type, update_user_id,update_user_real_name FROM excel_info WHERE parent_id=? AND is_delete=0 order by sort asc, excel_info_id asc`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}
+
+func GetNoContentExcelInfoListByConditionNoPage(condition string, pars []interface{}) (items []*ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT excel_info_id,excel_classify_id,excel_name,
+             unique_code,sys_user_id,sys_user_real_name,sort,is_join_permission, parent_id, balance_type, update_user_id,update_user_real_name,rel_excel_info_id,version_name FROM excel_info WHERE is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+
+	sql += ` ORDER BY excel_info_id DESC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetNoContentExcelListByConditionNoPage 获取没有content的excel表格列表数据
+func GetNoContentExcelListByConditionNoPage(condition string, pars []interface{}) (item []*MyExcelInfoList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT excel_info_id,source,excel_type,excel_name,unique_code,excel_classify_id,sys_user_id,sys_user_real_name,excel_image,file_url,sort,create_time,modify_time,is_join_permission,update_user_id,update_user_real_name
+FROM excel_info WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	//sql += " ORDER BY sort ASC,chart_info_id DESC LIMIT ?,? "
+	sql += " ORDER BY create_time DESC"
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}
+
+type ExcelInfoRuleMappingView struct {
+	ExcelInfoRuleMappingId int    `orm:"pk" description:"主键"`
+	ExcelInfoId            int    `description:"Excel信息ID"`
+	RuleType               int    `description:"规则类型"`
+	LeftValue              string `description:"左值"`
+	LeftValueBack          string `description:"左值后端存储" json:"-"`
+	LeftValueType          int    `description:"左值类型"`
+	RightValue             string `description:"右值"`
+	RightValueBack         string `description:"右值后端存储" json:"-"`
+	RightValueType         int    `description:"右值类型"`
+	FontColor              string `description:"字体颜色"`
+	BackgroundColor        string `description:"背景颜色"`
+	Remark                 string `description:"预设颜色说明"`
+	RemarkEn               string `description:"预设颜色英文说明"`
+	Scope                  string `description:"作用范围"`
+	ScopeCoord             string `description:"作用范围坐标"`
+	ScopeShow              string `description:"作用范围坐标前端显示"`
+	CreateTime             string `description:"创建时间"`
+}
+
+// GetExcelRuleMappingByExcelInfoId 根据excelInfoId获取规则映射信息
+func GetExcelRuleMappingByExcelInfoId(id int) (items []*ExcelInfoRuleMappingView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_info_rule_mapping WHERE excel_info_id = ? ORDER BY create_time ASC`
+	_, err = o.Raw(sql, id).QueryRows(&items)
+	return
+}
+
+// SearchExcelInfo 表格搜索
+type SearchExcelInfo struct {
+	ExcelInfo
+	SearchText    string                `description:"搜索结果(含高亮)"`
+	HaveOperaAuth bool                  `description:"是否有数据权限"`
+	Button        ExcelInfoDetailButton `description:"操作权限"`
+	CanEdit       bool                  `description:"是否可编辑"`
+	Editor        string                `description:"编辑人"`
+}
+
+// ExcelCommonExtraConfig 表格额外配置
+type ExcelCommonExtraConfig struct {
+	TableFreeze ExcelInfoFreeze `description:"表格冻结"`
+}
+
+// ExcelInfoFreeze 表格冻结
+type ExcelInfoFreeze struct {
+	FreezeFirstRow bool `description:"冻结首行"`
+	FreezeFirstCol bool `description:"冻结首列"`
+	FreezeStartRow int  `description:"冻结开始行"`
+	FreezeEndRow   int  `description:"冻结结束行"`
+	FreezeStartCol int  `description:"冻结开始列"`
+	FreezeEndCol   int  `description:"冻结结束列"`
+}

+ 52 - 29
models/data_manage/excel/response/excel_info.go

@@ -61,35 +61,36 @@ type TableDetailResp struct {
 
 // ExcelInfoDetail excel表格详情(前端使用)
 type ExcelInfoDetail struct {
-	ExcelInfoId        int                          `orm:"column(excel_info_id);pk"`
-	Source             int                          `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
-	ExcelType          int                          `description:"表格类型,1:指标列,2:日期列,默认:1"`
-	ExcelName          string                       `description:"表格名称"`
-	UniqueCode         string                       `description:"表格唯一编码"`
-	ExcelClassifyId    int                          `description:"表格分类id"`
-	SysUserId          int                          `description:"操作人id"`
-	SysUserRealName    string                       `description:"操作人真实姓名"`
-	Content            string                       `description:"表格内容"`
-	ExcelImage         string                       `description:"表格图片"`
-	FileUrl            string                       `description:"表格下载地址"`
-	Sort               int                          `description:"排序字段,数字越小越排前面"`
-	IsDelete           int                          `description:"是否删除,0:未删除,1:已删除"`
-	ModifyTime         time.Time                    `description:"最近修改日期"`
-	CreateTime         time.Time                    `description:"创建日期"`
-	TableData          interface{}                  `description:"表格内容"`
-	Button             excel2.ExcelInfoDetailButton `description:"操作权限"`
-	CanEdit            bool                         `description:"是否可编辑"`
-	Editor             string                       `description:"编辑人"`
-	IsJoinPermission   int                          `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
-	HaveOperaAuth      bool                         `description:"是否有数据权限"`
-	ParentId           int                          `description:"表格的父级id"`
-	BalanceType        int                          `description:"平衡表类型:0 动态表,1静态表"`
-	UpdateUserId       int                          `description:"更新人id"`
-	UpdateUserRealName string                       `description:"更新人真实姓名"`
-	RelExcelInfoId     int                          `description:"平衡表里静态表关联的动态表excel id"`
-	SourcesFrom        string                       `description:"图表来源"`
-	ExcelSource        string                       `description:"表格来源str"`
-	ExcelSourceEn      string                       `description:"表格来源(英文)"`
+	ExcelInfoId        int                           `orm:"column(excel_info_id);pk"`
+	Source             int                           `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
+	ExcelType          int                           `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName          string                        `description:"表格名称"`
+	UniqueCode         string                        `description:"表格唯一编码"`
+	ExcelClassifyId    int                           `description:"表格分类id"`
+	SysUserId          int                           `description:"操作人id"`
+	SysUserRealName    string                        `description:"操作人真实姓名"`
+	Content            string                        `description:"表格内容"`
+	ExcelImage         string                        `description:"表格图片"`
+	FileUrl            string                        `description:"表格下载地址"`
+	Sort               int                           `description:"排序字段,数字越小越排前面"`
+	IsDelete           int                           `description:"是否删除,0:未删除,1:已删除"`
+	ModifyTime         time.Time                     `description:"最近修改日期"`
+	CreateTime         time.Time                     `description:"创建日期"`
+	TableData          interface{}                   `description:"表格内容"`
+	Button             excel2.ExcelInfoDetailButton  `description:"操作权限"`
+	CanEdit            bool                          `description:"是否可编辑"`
+	Editor             string                        `description:"编辑人"`
+	IsJoinPermission   int                           `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	HaveOperaAuth      bool                          `description:"是否有数据权限"`
+	ParentId           int                           `description:"表格的父级id"`
+	BalanceType        int                           `description:"平衡表类型:0 动态表,1静态表"`
+	UpdateUserId       int                           `description:"更新人id"`
+	UpdateUserRealName string                        `description:"更新人真实姓名"`
+	RelExcelInfoId     int                           `description:"平衡表里静态表关联的动态表excel id"`
+	SourcesFrom        string                        `description:"图表来源"`
+	ExcelSource        string                        `description:"表格来源str"`
+	ExcelSourceEn      string                        `description:"表格来源(英文)"`
+	ExtraConfig        excel2.ExcelCommonExtraConfig `description:"表格额外配置"`
 }
 
 // ExcelInfoDetailButton 操作按钮
@@ -102,3 +103,25 @@ type ExcelInfoDetailButton struct {
 	OpEdbButton      bool `description:"是否可生成指标"`
 	RefreshEdbButton bool `description:"是否可刷新指标"`
 }
+
+type BalanceChildTableResp struct {
+	List []*excel2.ExcelInfo
+}
+
+type BalanceTableVersionListItem struct {
+	ExcelInfoId    int    `description:"表格id"`
+	UniqueCode     string `description:"表格唯一编码"`
+	BalanceType    int    `description:"平衡表类型:0 动态表,1静态表"`
+	RelExcelInfoId int    `description:"平衡表里静态表关联的动态表excel id"`
+	VersionName    string `description:"静态表版本名称"`
+}
+
+type BalanceTableVersionListResp struct {
+	List []*BalanceTableVersionListItem
+}
+
+// SearchExcelListResp 搜索表格列表返回数据
+type SearchExcelListResp struct {
+	Paging *paging.PagingItem
+	List   []*excel2.SearchExcelInfo
+}

+ 62 - 0
models/data_manage/factor_edb_series.go

@@ -348,3 +348,65 @@ func (a FactorEdbSeriesCorrelationMatrixOrder) Less(i, j int) bool {
 	// 负数按绝对值的降序排序(即数值的升序)
 	return a[i].XData > a[j].XData
 }
+
+// RemoveSeriesAndMappingByFactorEdbSeriesId 删除系列和指标关联
+func (m *FactorEdbSeries) RemoveSeriesAndMappingByFactorEdbSeriesId(factorEdbSeriesChartMapping *FactorEdbSeriesChartMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+	factorEdbSeriesId := factorEdbSeriesChartMapping.FactorEdbSeriesId
+	err = factorEdbSeriesChartMapping.Remove()
+	if err != nil {
+		err = fmt.Errorf("factorEdbSeriesChartMapping.delete err: %v", err)
+		return
+	}
+	if factorEdbSeriesId == 0 {
+		return
+	}
+
+	// 清除原指标关联
+	seriesOb := new(FactorEdbSeries)
+	cond := fmt.Sprintf("%s = ?", seriesOb.Cols().PrimaryId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, factorEdbSeriesId)
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, seriesOb.TableName(), cond)
+	_, e = tx.Raw(sql, pars).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove FactorEdbSeries err: %v", e)
+		return
+	}
+
+	// 清除原指标关联
+	mappingOb := new(FactorEdbSeriesMapping)
+	cond1 := fmt.Sprintf("%s = ?", mappingOb.Cols().FactorEdbSeriesId)
+	pars1 := make([]interface{}, 0)
+	pars1 = append(pars1, factorEdbSeriesId)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s`, mappingOb.TableName(), cond1)
+	_, e = tx.Raw(sql, pars1).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+	dataOb := new(FactorEdbSeriesCalculateDataQjjs)
+	//删除原指标数据
+	cond2 := fmt.Sprintf("%s = ?", dataOb.Cols().FactorEdbSeriesId)
+	pars2 := make([]interface{}, 0)
+	pars2 = append(pars2, factorEdbSeriesId)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s`, dataOb.TableName(), cond2)
+	_, e = tx.Raw(sql, pars2).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+	return
+}

+ 210 - 0
models/data_manage/factor_edb_series_calculate_data_qjjs.go

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

+ 8 - 0
models/data_manage/factor_edb_series_chart_mapping.go

@@ -10,6 +10,7 @@ import (
 
 const (
 	FactorEdbSeriesChartCalculateTypeCorrelation = 1 // 相关性计算
+	FactorEdbSeriesChartCalculateTypeRange       = 2 // 	区间计算
 )
 
 // FactorEdbSeriesChartMapping 因子指标系列-图表关联
@@ -165,3 +166,10 @@ func (m *FactorEdbSeriesChartMapping) GetDistinctSeriesIdByChartId(chartId int)
 	_, err = o.Raw(sql, chartId).QueryRows(&seriesIds)
 	return
 }
+
+func (m *FactorEdbSeriesChartMapping) GetItemByChartInfoId(id int) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().ChartInfoId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}

+ 8 - 0
models/data_manage/multiple_graph_config_edb_mapping.go

@@ -51,3 +51,11 @@ func GetMultipleGraphConfigEdbMappingListById(configId int) (items []*MultipleGr
 
 	return
 }
+
+func GetMultipleGraphConfigEdbMappingListByIdAndSource(configId, source int) (items []*MultipleGraphConfigEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? AND source = ? `
+	_, err = o.Raw(sql, configId, source).QueryRows(&items)
+
+	return
+}

+ 12 - 0
models/data_manage/my_chart.go

@@ -329,6 +329,7 @@ type MyChartList struct {
 	Source              int    `description:"1:ETA图库;2:商品价格曲线"`
 	IsJoinPermission    int    `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth       bool   `description:"是否有数据权限,默认:false"`
+	SearchText          string `description:"搜索结果(含高亮)"`
 }
 
 type MyChartListResp struct {
@@ -975,3 +976,14 @@ func GetChartItems(cond string, pars []interface{}, orderRule string) (item []*C
 	_, err = o.Raw(sql, pars).QueryRows(&item)
 	return
 }
+
+func GetChartInfoViewByIdList(chartInfoIdList []int) (items []*ChartInfoView, err error) {
+	num := len(chartInfoIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_info WHERE chart_info_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, chartInfoIdList).QueryRows(&items)
+	return
+}

+ 27 - 6
models/db.go

@@ -11,7 +11,9 @@ import (
 	"eta/eta_mobile/models/data_manage/supply_analysis"
 	"eta/eta_mobile/models/data_stat"
 	"eta/eta_mobile/models/eta_trial"
+	"eta/eta_mobile/models/material"
 	"eta/eta_mobile/models/ppt_english"
+	"eta/eta_mobile/models/report"
 	"eta/eta_mobile/models/report_approve"
 	"eta/eta_mobile/models/sandbox"
 	"eta/eta_mobile/models/semantic_analysis"
@@ -196,6 +198,9 @@ func init() {
 	// 初始化因子指标系列
 	initFactorEdbSeries()
 
+	// 注册素材库表
+	initMaterial()
+
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
 	afterInitTable()
 }
@@ -248,12 +253,15 @@ func initReport() {
 		new(ChartPermission),                     // 权限表
 		new(YbPcSuncode),
 		new(YbSuncodePars),
-		new(ReportAuthor),                  //报告作者
-		new(ClassifyMenu),                  // 报告分类-子目录表
-		new(ClassifyMenuRelation),          // 报告分类-子目录关联表
-		new(ChartPermissionChapterMapping), // 权限mapping表
-		new(ReportChapterType),             // 报告章节类型表
-		new(ReportStateRecord),             // 研报状态修改记录表
+		new(ReportAuthor),                          //报告作者
+		new(ClassifyMenu),                          // 报告分类-子目录表
+		new(ClassifyMenuRelation),                  // 报告分类-子目录关联表
+		new(ChartPermissionChapterMapping),         // 权限mapping表
+		new(ReportChapterType),                     // 报告章节类型表
+		new(ReportStateRecord),                     // 研报状态修改记录表
+		new(report.ReportGrant),                    // 报告授权用户表
+		new(report.ReportChapterGrant),             // 报告章节授权用户表
+		new(report.ReportChapterPermissionMapping), // 报告章节的权限关系表
 	)
 }
 
@@ -601,6 +609,16 @@ func initFactorEdbSeries() {
 		new(data_manage.FactorEdbSeriesChartMapping),  // 因子指标系列-图表关联
 		new(data_manage.FactorEdbSeriesMapping),       // 因子指标系列-指标计算数据
 		new(data_manage.FactorEdbSeriesCalculateData), // 因子指标系列-指标关联
+		new(data_manage.FactorEdbSeriesCalculateDataQjjs),
+	)
+}
+
+// initMaterial 注册素材库表
+func initMaterial() {
+	//注册对象
+	orm.RegisterModel(
+		new(material.Material),         //素材库表
+		new(material.MaterialClassify), //素材库分类表
 	)
 }
 
@@ -614,4 +632,7 @@ func afterInitTable() {
 
 	// 初始化是否启用mongo配置
 	InitUseMongoConf()
+
+	// 初始化商家基本配置
+	InitBusinessConf()
 }

+ 162 - 0
models/document_manage_model/outside_report.go

@@ -0,0 +1,162 @@
+// @Author gmy 2024/9/19 14:53:00
+package document_manage_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type OutsideReport struct {
+	OutsideReportId  int    `orm:"column(outside_report_id);pk" description:"外部报告ID"`
+	Source           int    `orm:"column(source)" description:"来源,1:ETA系统录入;2:API接口录入;3:邮件监听录入"`
+	Title            string `orm:"column(title)" description:"报告标题"`
+	Abstract         string `orm:"column(abstract)" description:"摘要"`
+	ClassifyId       int    `orm:"column(classify_id)" description:"所属分类id"`
+	ClassifyName     string `orm:"column(classify_name)" description:"所属分类名称(整个分类链条)"`
+	Content          string `orm:"column(content)" description:"报告富文本内容"`
+	SysUserId        int    `orm:"column(sys_user_id)" description:"创建人id"`
+	SysUserName      string `orm:"column(sys_user_name)" description:"创建人姓名"`
+	EmailMessageUid  int    `orm:"column(email_message_uid)" description:"该邮件在邮箱中的唯一id"`
+	ReportUpdateTime string `orm:"column(report_update_time)" description:"报告更新时间,如果来源于邮件,那么取邮件的收件时间"`
+	ModifyTime       string `orm:"column(modify_time)" description:"最近一次修改时间"`
+	CreateTime       string `orm:"column(create_time)" description:"创建时间"`
+	ReportCode       string `orm:"column(report_code)" description:"报告唯一编码"`
+}
+
+type OutsideReportResp struct {
+	OutsideReport
+	IsCollect int `description:"是否收藏"`
+}
+
+type OutsideReportPage struct {
+	List   []OutsideReportResp `description:"报告列表"`
+	Paging *paging.PagingItem  `description:"分页数据"`
+}
+
+type OutsideReportBO struct {
+	OutsideReportId int    `orm:"column(outside_report_id);pk" description:"外部报告ID"`
+	Source          int    `orm:"column(source)" description:"来源,1:ETA系统录入;2:API接口录入;3:邮件监听录入"`
+	Title           string `orm:"column(title)" description:"报告标题"`
+	Abstract        string `orm:"column(abstract)" description:"摘要"`
+	ClassifyId      int    `orm:"column(classify_id)" description:"所属分类id"`
+	ClassifyName    string `orm:"column(classify_name)" description:"所属分类名称(整个分类链条)"`
+	Content         string `orm:"column(content)" description:"报告富文本内容"`
+	SysUserId       int    `orm:"column(sys_user_id)" description:"创建人id"`
+	SysUserName     string `orm:"column(sys_user_name)" description:"创建人姓名"`
+	ModifyTime      string `orm:"column(modify_time)" description:"最近一次修改时间"`
+	CreateTime      string `orm:"column(create_time)" description:"创建时间"`
+	ReportCode      string `orm:"column(report_code)" description:"报告唯一编码"`
+	AttachmentList  []*OutsideReportAttachment
+}
+
+// 在 init 函数中注册模型
+func init() {
+	orm.RegisterModel(new(OutsideReport))
+}
+
+// GetOutsideReportListByConditionCount 根据条件查询列表条数
+func GetOutsideReportListByConditionCount(condition string, pars []interface{}, chartPermissionIdList []string) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var sql string
+	if len(chartPermissionIdList) > 0 {
+		sql = `select count(1) from (`
+	}
+	sql += `SELECT COUNT( DISTINCT t1.outside_report_id ) 
+			FROM outside_report t1 
+			    LEFT JOIN chart_permission_search_key_word_mapping t2 ON t1.classify_id = t2.classify_id 
+			WHERE 1 = 1 `
+	sql += condition
+	if len(chartPermissionIdList) > 0 {
+		sql += ` ) t`
+	}
+
+	err = o.Raw(sql, pars).QueryRow(&count)
+	if err != nil && err != orm.ErrNoRows {
+		return 0, err
+	}
+
+	return count, err
+}
+
+// GetOutsideReportListByCondition 根据条件查询列表
+func GetOutsideReportListByCondition(condition string, pars []interface{}, currentIndex int, pageSize int) (list []OutsideReportResp, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select DISTINCT t1.outside_report_id, t1.source, t1.title, t1.abstract, t1.classify_id, t1.classify_name, t1.sys_user_id, t1.sys_user_name, t1.email_message_uid, t1.report_update_time, t1.modify_time, t1.create_time, t1.report_code  from outside_report t1 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
+	sql += condition
+	sql += ` limit ?, ?`
+	_, err = o.Raw(sql, pars, (currentIndex-1)*pageSize, pageSize).QueryRows(&list)
+	if err != nil && err != orm.ErrNoRows {
+		return nil, err
+	}
+
+	return list, err
+}
+
+/*// GetOutsideReportListByDocumentTypeCount 根据文档类型查询列表条数
+func GetOutsideReportListByDocumentTypeCount(documentType int, condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var sql string
+	if documentType == 1 {
+		sql = `select count(t1.OutsideReportId) from outside_report t1 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
+	} else if documentType == 2 {
+		sql = `select count(t1.OutsideReportId) from outside_report t1 left join chart_permission_search_key_word_mapping t2  on t1.classify_id = t2.classify_id left join user_collect_classify t3 on t1.classify_id = t3.classify_id where 1 = 1 `
+	}
+
+	sql += sql + condition
+	err = o.Raw(sql, pars).QueryRow(&count)
+	if err != nil {
+		return 0, err
+	}
+
+	return 0, err
+}
+
+// GetOutsideReportListByDocumentType 根据文档类型查询列表
+func GetOutsideReportListByDocumentType(documentType int, condition string, pars []interface{}) (list []OutsideReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var sql string
+	if documentType == 1 {
+		sql = `select t1.* from outside_report t1 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
+	} else if documentType == 2 {
+		sql = `select t1.* from outside_report t1 left join chart_permission_search_key_word_mapping t2  on t1.classify_id = t2.classify_id left join user_collect_classify t3 on t1.classify_id = t3.classify_id where 1 = 1 `
+	}
+
+	sql += condition
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	if err != nil {
+		return nil, err
+	}
+
+	return list, err
+}*/
+
+// SaveOutsideReport 保存报告
+func SaveOutsideReport(outsideReport OutsideReport) (id int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err = o.Insert(&outsideReport)
+	return
+}
+
+// GetOutsideReportById 根据ID获取报告
+func GetOutsideReportById(id int) (outsideReport *OutsideReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	outsideReport = &OutsideReport{}
+
+	err = o.QueryTable("outside_report").Filter("outside_report_id", id).One(outsideReport)
+	return
+}
+
+// UpdateOutsideReport 更新报告
+func UpdateOutsideReport(outsideReport *OutsideReport) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(&outsideReport)
+	return
+}
+
+// DeleteOutsideReport 删除报告
+func DeleteOutsideReport(id int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.QueryTable("outside_report").Filter("outside_report_id", id).Delete()
+	return
+}

+ 46 - 0
models/document_manage_model/outside_report_attachment.go

@@ -0,0 +1,46 @@
+// @Author gmy 2024/9/19 15:13:00
+package document_manage_model
+
+import "github.com/beego/beego/v2/client/orm"
+
+type OutsideReportAttachment struct {
+	OutsideReportAttachmentId int    `orm:"column(outside_report_attachment_id);pk" description:"外部报告附件ID"`
+	OutsideReportId           int    `orm:"column(outside_report_id)" description:"报告id"`
+	Title                     string `orm:"column(title)" description:"附件名称"`
+	Url                       string `orm:"column(url)" description:"附件地址"`
+	CreateTime                string `orm:"column(create_time)" description:"附件新增时间"`
+	FileSize                  int64  `orm:"column(file_size)" description:"附件大小"`
+}
+
+// 在 init 函数中注册模型
+func init() {
+	orm.RegisterModel(new(OutsideReportAttachment))
+}
+
+// SaveOutsideReportAttachment 保存附件
+func SaveOutsideReportAttachment(attachment *OutsideReportAttachment) (int64, error) {
+	o := orm.NewOrmUsingDB("rddp")
+	return o.Insert(attachment)
+}
+
+// GetOutsideReportAttachmentListByReportId 根据报告id获取附件列表
+func GetOutsideReportAttachmentListByReportId(outsideReportId int) ([]*OutsideReportAttachment, error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var attachmentList []*OutsideReportAttachment
+	_, err := o.QueryTable("outside_report_attachment").
+		Filter("outside_report_id", outsideReportId).
+		All(&attachmentList)
+	if err != nil {
+		return nil, err
+	}
+	return attachmentList, nil
+}
+
+// DeleteReportAttachmentByReportId 根据报告id删除附件
+func DeleteReportAttachmentByReportId(outsideReportId int) error {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err := o.QueryTable("outside_report_attachment").
+		Filter("outside_report_id", outsideReportId).
+		Delete()
+	return err
+}

+ 77 - 3
models/english_report.go

@@ -36,6 +36,7 @@ type EnglishReport struct {
 	ReportCode         string    `description:"报告唯一编码"`
 	Pv                 int       `description:"Pv"`
 	PvEmail            int       `description:"邮箱PV"`
+	UvEmail            int       `description:"邮箱UV"`
 	EmailState         int       `description:"群发邮件状态: 0-未发送; 1-已发送"`
 	Overview           string    `description:"英文概述部分"`
 	KeyTakeaways       string    `description:"关键点"`
@@ -44,6 +45,11 @@ type EnglishReport struct {
 	AdminRealName      string    `description:"创建者姓名"`
 	ApproveTime        time.Time `description:"审批时间"`
 	ApproveId          int       `description:"审批ID"`
+	DetailImgUrl       string    `description:"报告详情长图地址"`
+	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string    `description:"报告详情PDF地址-手机端"`
+	EmailHasFail       int       `description:"是否存在邮件发送失败的记录: 0-否; 1-是"`
 }
 
 func GetEnglishReportStage(classifyIdFirst, classifyIdSecond int) (count int, err error) {
@@ -91,6 +97,8 @@ type AddEnglishReportReq struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdThird    int    `description:"三级分类id"`
+	ClassifyNameThird  string `description:"三级分类名称"`
 	Title              string `description:"标题"`
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
@@ -255,6 +263,7 @@ type EnglishReportList struct {
 	Pv                 int       `description:"Pv"`
 	ShareUrl           string    `description:"分享url"`
 	PvEmail            int       `description:"邮箱PV"`
+	UvEmail            int       `description:"邮箱UV"`
 	EmailState         int       `description:"群发邮件状态: 0-未发送; 1-已发送"`
 	EmailAuth          bool      `description:"是否有权限群发邮件"`
 	EmailHasFail       bool      `description:"是否存在邮件发送失败的记录"`
@@ -269,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 {
@@ -294,7 +305,7 @@ func GetEnglishReportListCount(condition string, pars []interface{}, companyType
 	return
 }
 
-func GetEnglishReportList(condition string, pars []interface{}, companyType string, startSize, pageSize int, fieldArr []string) (items []*EnglishReportList, err error) {
+func GetEnglishReportList(condition string, pars []interface{}, companyType string, startSize, pageSize int, fieldArr []string) (items []*EnglishReport, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	//产品权限
 	companyTypeSqlStr := ``
@@ -314,7 +325,7 @@ func GetEnglishReportList(condition string, pars []interface{}, companyType stri
 		sql += condition
 	}
 	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
-	sql += `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
+	sql += ` ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
 	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
 	return
 }
@@ -930,10 +941,73 @@ 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")
 	sql := `UPDATE english_report SET detail_img_url = '',detail_pdf_url='',modify_time=NOW() WHERE id = ? `
 	_, err = o.Raw(sql, reportId).Exec()
 	return
-}
+}
+
+func FormatEnglishReport2ListItem(origin *EnglishReport) (item *EnglishReportList) {
+	if origin == nil {
+		return
+	}
+	item = new(EnglishReportList)
+	item.Id = origin.Id
+	item.AddType = origin.AddType
+	item.ClassifyIdFirst = origin.ClassifyIdFirst
+	item.ClassifyNameFirst = origin.ClassifyNameFirst
+	item.ClassifyIdSecond = origin.ClassifyIdSecond
+	item.ClassifyNameSecond = origin.ClassifyNameSecond
+	item.Title = origin.Title
+	item.Abstract = origin.Abstract
+	item.Author = origin.Author
+	item.Frequency = origin.Frequency
+	item.CreateTime = origin.CreateTime
+	item.ModifyTime = origin.ModifyTime
+	item.State = origin.State
+	item.PublishTime = utils.TimeTransferString(utils.FormatDateTime, origin.PublishTime)
+	item.PrePublishTime = utils.TimeTransferString(utils.FormatDateTime, origin.PrePublishTime)
+	item.Stage = origin.Stage
+	//item.Content = origin.Content
+	item.VideoUrl = origin.VideoUrl
+	item.VideoName = origin.VideoName
+	item.VideoPlaySeconds = origin.VideoPlaySeconds
+	item.ContentSub = origin.ContentSub
+	item.ReportCode = origin.ReportCode
+	item.Pv = origin.Pv
+	item.PvEmail = origin.PvEmail
+	item.UvEmail = origin.UvEmail
+	// 邮箱PV大于0的时候, 不展示最初版本的PV
+	if item.PvEmail > 0 {
+		item.Pv = 0
+	}
+	item.EmailState = origin.EmailState
+	if origin.EmailHasFail == 1 {
+		item.EmailHasFail = true
+	}
+	item.FromReportId = origin.FromReportId
+	item.AdminId = origin.AdminId
+	item.AdminRealName = origin.AdminRealName
+	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
+}

+ 245 - 0
models/material/material.go

@@ -0,0 +1,245 @@
+package material
+
+import (
+	"eta/eta_mobile/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type Material struct {
+	MaterialId      int       `orm:"column(material_id);pk" description:"素材id"`
+	MaterialName    string    `description:"素材名称"`
+	MaterialNameEn  string    `description:"英文素材名称"`
+	ImgUrl          string    `description:"素材图片地址"`
+	SysUserId       int       `description:"作者id"`
+	SysUserRealName string    `description:"作者名称"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+	ClassifyId      int       `description:"分类id"`
+	Sort            int       `description:"排序"`
+}
+
+// Update 素材字段变更
+func (material *Material) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(material, cols...)
+	return
+}
+
+// GetMaterialById 根据素材id获取素材详情
+func GetMaterialById(MaterialId int) (materialInfo *Material, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * FROM material where material_id = ?`
+	err = o.Raw(sql, MaterialId).QueryRow(&materialInfo)
+	return
+}
+func DeleteByMaterialIds(ids []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `DELETE FROM material WHERE material_id in (` + utils.GetOrmInReplace(len(ids)) + `)`
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func UpdateClassifyByMaterialIds(ids []int, classifyId int, now time.Time) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE material SET classify_id=?, modify_time=? WHERE material_id in (` + utils.GetOrmInReplace(len(ids)) + `) and classify_id != ?`
+	_, err = o.Raw(sql, classifyId, now, ids, classifyId).Exec()
+	return
+}
+
+// AddMultiMaterial 批量添加素材
+func AddMultiMaterial(materialInfoList []*Material) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(utils.MultiAddNum, materialInfoList)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// MaterialListItem 素材推演列表数据
+type MaterialListItem struct {
+	MaterialId      int    `description:"素材id"`
+	MaterialName    string `description:"素材名称"`
+	MaterialNameEn  string `description:"英文素材名称"`
+	ImgUrl          string `description:"素材图片地址"`
+	ModifyTime      string `description:"修改时间"`
+	CreateTime      string `description:"创建时间"`
+	SysUserId       int    `description:"作者id"`
+	SysUserRealName string `description:"作者名称"`
+}
+
+// MaterialSaveResp 保存素材响应体
+type MaterialSaveResp struct {
+	*Material
+}
+
+type MaterialListItems struct {
+	Material
+	ModifyTime string `description:"修改时间"`
+	CreateTime string `description:"创建时间"`
+	//ParentIds  string
+}
+
+func GetMaterialListPageByCondition(condition string, pars []interface{}, startSize, pageSize int) (item []*MaterialListItems, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " ORDER BY create_time DESC, material_id DESC LIMIT ?,? "
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&item)
+	return
+}
+
+func GetMaterialListByCondition(condition string, pars []interface{}) (item []*MaterialListItems, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " ORDER BY create_time DESC, material_id DESC"
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}
+
+func GetMaterialListCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS count FROM material WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+type MaterialListResp struct {
+	Paging *paging.PagingItem
+	List   []*MaterialListItems
+}
+
+func AddMaterial(item *Material) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}
+
+// GetMaterialByClassifyIdAndName 根据分类id和素材名获取图表信息
+func GetMaterialByClassifyIdAndName(classifyId int, name string) (item *Material, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material WHERE classify_id = ? and material_name=? `
+	err = o.Raw(sql, classifyId, name).QueryRow(&item)
+	return
+}
+
+// GetMaterialByIds 根据素材id获取素材信息
+func GetMaterialByIds(ids []int) (items []*MaterialListItems, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material WHERE material_id in (` + utils.GetOrmInReplace(len(ids)) + `) `
+	_, err = o.Raw(sql, ids).QueryRows(&items)
+	return
+}
+
+// SaveAsMaterialReq 添加素材的请求数据
+type SaveAsMaterialReq struct {
+	MaterialName string `description:"素材名称"`
+	ClassifyId   int    `description:"分类id"`
+	ObjectId     int    `description:"对象id"`
+	ObjectType   string `description:"对象类型:chart,excel,sandbox,sa_doc"`
+}
+
+// MyChartSaveAsMaterialReq 添加素材的
+type MyChartSaveAsMaterialReq struct {
+	MaterialList []*MyChartSaveAsMaterialItem
+}
+
+type MyChartSaveAsMaterialItem struct {
+	MaterialName string `description:"素材名称"`
+	ChartInfoId  int    `description:"图表id"`
+	MyChartId    int    `description:"我的图表ID"`
+	ClassifyId   int    `description:"分类id"`
+}
+
+// BatchAddMaterialReq 批量添加素材的请求数据
+type BatchAddMaterialReq struct {
+	MaterialList []BatchAddMaterialItem
+	ClassifyId   int `description:"分类id"`
+}
+
+type BatchAddMaterialItem struct {
+	MaterialName string `description:"素材名称"`
+	ImgUrl       string `description:"素材图片地址"`
+}
+
+// DeleteMaterial 删除素材的请求数据
+type DeleteMaterial struct {
+	MaterialId int `description:"素材id"`
+}
+
+// BatchDeleteMaterialReq 删除素材的请求数据
+type BatchDeleteMaterialReq struct {
+	MaterialIds []int  `description:"素材id"`
+	ClassifyId  int    `description:"分类id"`
+	IsShowMe    bool   `description:"操作人id,支持多选,用英文,隔开"`
+	Keyword     string `description:"关键字"`
+	IsSelectAll bool   `description:"是否选择所有素材"`
+}
+
+type BatchChangeClassifyMaterialReq struct {
+	BatchDeleteMaterialReq
+	NewClassifyId int `description:"新分类ID"`
+}
+
+type ChangeClassifyMaterialReq struct {
+	MaterialId    int `description:"素材id"`
+	NewClassifyId int `description:"新分类ID"`
+}
+
+// RenameMaterialReq 添加/编辑素材的请求数据
+type RenameMaterialReq struct {
+	MaterialId   int    `description:"素材id"`
+	MaterialName string `description:"素材名称"`
+}
+
+func GetMaterialInfoCountByClassifyIds(classifyIds []string) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS count FROM material WHERE classify_id in(` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	err = o.Raw(sql, classifyIds).QueryRow(&count)
+	return
+}
+
+func GetMaterialByNames(materialNames []string) (items []*Material, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM material WHERE material_name in (` + utils.GetOrmInReplace(len(materialNames)) + `) limit 1`
+	_, err = o.Raw(sql, materialNames).QueryRows(&items)
+	return
+}
+
+func GetMaterialByNameEns(materialNames []string) (items []*Material, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM material WHERE material_name_en in (` + utils.GetOrmInReplace(len(materialNames)) + `) limit 1`
+	_, err = o.Raw(sql, materialNames).QueryRows(&items)
+	return
+}
+
+func GetMaterialByName(materialName string) (item *Material, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM material WHERE material_name = ?`
+	err = o.Raw(sql, materialName).QueryRow(&item)
+	return
+}
+func GetMaterialByNameEn(materialName string) (item *Material, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM material WHERE material_name_en = ?`
+	err = o.Raw(sql, materialName).QueryRow(&item)
+	return
+}
+
+// GetMaterialMaxSort 获取最大的排序数
+func GetMaterialMaxSort() (sort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT MAX(sort) AS sort FROM material `
+	err = o.Raw(sql).QueryRow(&sort)
+	return
+}

+ 215 - 0
models/material/material_classify.go

@@ -0,0 +1,215 @@
+package material
+
+import (
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type MaterialClassify struct {
+	ClassifyId      int       `orm:"column(classify_id);pk"`
+	ClassifyName    string    `description:"分类名称"`
+	ClassifyNameEn  string    `description:"英文分类名称"`
+	ParentId        int       `description:"父级id"`
+	CreateTime      time.Time `description:"创建时间"`
+	ModifyTime      time.Time `description:"修改时间"`
+	SysUserId       int       `description:"创建人id"`
+	SysUserRealName string    `description:"创建人姓名"`
+	Level           int       `description:"层级"`
+	Sort            int       `description:"排序字段,越小越靠前,默认值:10"`
+	LevelPath       string    `description:"层级路径"`
+}
+
+func AddMaterialClassify(item *MaterialClassify) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}
+
+// GetMaterialClassifyByParentId
+func GetMaterialClassifyByParentId(parentId int) (items []*MaterialClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material_classify WHERE parent_id=? order by sort asc,classify_id asc`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}
+
+// GetMaterialClassifyAll
+func GetMaterialClassifyAll() (items []*MaterialClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material_classify WHERE parent_id<>0 order by sort asc,classify_id asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+type MaterialClassifyItems struct {
+	ClassifyId     int       `orm:"column(classify_id);pk"`
+	ClassifyName   string    `description:"分类名称"`
+	ClassifyNameEn string    `description:"英文分类名称"`
+	ParentId       int       `description:"父级id"`
+	CreateTime     time.Time `description:"创建时间"`
+	ModifyTime     time.Time `description:"修改时间"`
+	SysUserId      int       `description:"创建人id"`
+	SysUserName    string    `description:"创建人姓名"`
+	Level          int       `description:"层级"`
+	Sort           int       `description:"排序字段,越小越靠前,默认值:10"`
+	Children       []*MaterialClassifyItems
+}
+
+type MaterialClassifyListResp struct {
+	AllNodes []*MaterialClassifyItems
+}
+
+type AddMaterialClassifyReq struct {
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级id,第一级传0"`
+	//Level               int    `description:"层级,第一级传0,其余传上一级的层级"`
+}
+
+func GetMaterialClassifyNameCount(classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM material_classify WHERE parent_id=? AND classify_name=? `
+	err = o.Raw(sql, parentId, classifyName).QueryRow(&count)
+	return
+}
+
+func GetMaterialClassifyNameEnCount(classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM material_classify WHERE parent_id=? AND classify_name_en=? `
+	err = o.Raw(sql, parentId, classifyName).QueryRow(&count)
+	return
+}
+
+func GetMaterialClassifyNameNotSelfCount(id int, classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM material_classify WHERE classify_id !=? AND parent_id=? AND classify_name=? `
+	err = o.Raw(sql, id, parentId, classifyName).QueryRow(&count)
+	return
+}
+
+func GetMaterialClassifyNameEnNotSelfCount(id int, classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM material_classify WHERE classify_id !=? AND parent_id=? AND classify_name_en=? `
+	err = o.Raw(sql, id, parentId, classifyName).QueryRow(&count)
+	return
+}
+
+// GetMaterialClassifyMaxSort 获取沙盘分类下最大的排序数
+func GetMaterialClassifyMaxSort(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT MAX(sort) AS sort FROM material_classify WHERE parent_id=? `
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+type EditMaterialClassifyReq struct {
+	ClassifyName string `description:"分类名称"`
+	ClassifyId   int    `description:"分类id"`
+}
+
+func GetMaterialClassifyById(classifyId int) (item *MaterialClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM material_classify WHERE classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+type MaterialClassifyDeleteCheckReq struct {
+	ClassifyId int `description:"分类id"`
+}
+
+type MaterialClassifyDeleteCheckResp struct {
+	DeleteStatus int    `description:"检测状态:0:默认值,如果为0,继续走其他校验,1:该分类下关联图表不可删除,2:确认删除当前目录及包含的子目录吗"`
+	TipsMsg      string `description:"提示信息"`
+}
+
+type DeleteMaterialClassifyReq struct {
+	ClassifyId int `description:"分类id"`
+	//	MaterialId int `description:"素材id"`
+}
+
+func DeleteMaterialClassify(classifyIds []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` DELETE FROM material_classify WHERE classify_id IN(` + utils.GetOrmInReplace(len(classifyIds)) + `) `
+	_, err = o.Raw(sql, classifyIds).Exec()
+	return
+}
+
+// MoveMaterialClassifyReq 移动沙盘分类请求参数
+type MoveMaterialClassifyReq struct {
+	ClassifyId int `description:"分类id"`
+	//MaterialId       int `description:"沙盘ID"`
+	ParentClassifyId int `description:"目标父级分类id"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类id"`
+	NextClassifyId   int `description:"下一个兄弟节点分类id"`
+	//PrevType         int `description:"上一个兄弟节点类型 1分类 2沙盘 "`
+	//NextType         int `description:"上一个兄弟节点类型 1分类 2沙盘 "`
+}
+
+// UpdateMaterialClassifySortByParentId 根据沙盘父类id更新排序
+func UpdateMaterialClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` update material_classify set sort = ` + updateSort + ` WHERE parent_id=? and sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstMaterialClassifyByParentId 获取当前父级沙盘分类下的排序第一条的数据
+func GetFirstMaterialClassifyByParentId(parentId int) (item *MaterialClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM material_classify WHERE parent_id=? order by sort asc,classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// Update 更新沙盘分类基础信息
+func (m *MaterialClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+// GetMaterialClassifyAndInfoByParentId
+func GetMaterialClassifyAndInfoByParentId(parentId int) (items []*MaterialClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT *
+FROM
+	material_classify 
+WHERE
+	parent_id = ?
+ORDER BY
+	sort ASC,
+	classify_id ASC`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}
+
+type SandboxLinkCheckReq struct {
+	EdbInfoIdList   []int `description:"指标id列表"`
+	ChartInfoIdList []int `description:"图库id列表"`
+	ReportIdList    []int `description:"报告id列表"`
+}
+
+type SandboxLinkCheckItem struct {
+	Id         int    `description:"id"`
+	Name       string `description:"名称"`
+	UniqueCode string `description:"唯一编码"`
+	ClassifyId int    `description:"分类id"`
+}
+
+type SandboxLinkCheckResp struct {
+	EdbInfoIdList   []*SandboxLinkCheckItem `description:"指标id列表"`
+	ChartInfoIdList []*SandboxLinkCheckItem `description:"图库id列表"`
+	ReportIdList    []*SandboxLinkCheckItem `description:"报告id列表"`
+}
+
+func GetMaterialClassifyByLevelPath(levelPath string) (items []*MaterialClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM material_classify where level_path like "` + levelPath + `%"`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

+ 26 - 0
models/move_interface.go

@@ -0,0 +1,26 @@
+package models
+
+import "time"
+
+// SingleMoveNodeReq 移动请求参数
+type SingleMoveNodeReq struct {
+	NodeId       int `description:"id"`
+	ParentNodeId int `description:"父级id"`
+	PrevNodeId   int `description:"上一个兄弟节点id"`
+	NextNodeId   int `description:"下一个兄弟节点id"`
+	NodeType     int
+	/*ChartInfoId      int `description:"图表ID, 如果图表ID有值,则移动对象为图表,否则认为移动对象分类"`
+	PrevChartInfoId  int `description:"上一个图表ID"`
+	NextChartInfoId  int `description:"下一个图表ID"`*/
+}
+
+type NodeInfo struct {
+	NodeId     int `description:"id"`
+	NodeName   string
+	ParentId   int `description:"父级id"`
+	Sort       int
+	Level      int
+	LevelPath  string
+	ModifyTime time.Time
+	//Update     func([]string) (err error)
+}

+ 95 - 7
models/permission.go

@@ -1,6 +1,9 @@
 package models
 
-import "github.com/beego/beego/v2/client/orm"
+import (
+	"eta/eta_mobile/utils"
+	"github.com/beego/beego/v2/client/orm"
+)
 
 // ChartPermissionSearchKeyWordMapping 权限相关
 type ChartPermissionSearchKeyWordMapping struct {
@@ -10,12 +13,58 @@ type ChartPermissionSearchKeyWordMapping struct {
 	From               string `description:"类型标识" json:"-"`
 	TacticType         string `description:"策略表type字段值" json:"-"`
 	TeleconferenceSort int    `description:"电话会类型排序" json:"-"`
+	ClassifyId         int    `description:"分类ID"`
 }
 
-func GetPermission(classifyNameSecond string) (items []*ChartPermissionSearchKeyWordMapping, err error) {
+func GetPermission(classifyId int) (items []*ChartPermissionSearchKeyWordMapping, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT * FROM chart_permission_search_key_word_mapping AS a WHERE a.from='rddp' AND a.key_word=? `
-	_, err = o.Raw(sql, classifyNameSecond).QueryRows(&items)
+	sql := `SELECT * FROM chart_permission_search_key_word_mapping AS a WHERE a.from='rddp' AND a.classify_id = ? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+func GetAllPermissionMapping() (items []*ChartPermissionSearchKeyWordMapping, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission_search_key_word_mapping AS a WHERE a.from='rddp'`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// EditChartPermissionSearchKeyWordMappingMulti 修改报告报告权限(先删除原有的权限,再添加新的权限)
+func EditChartPermissionSearchKeyWordMappingMulti(keyword string, permissionIdList []int, classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := "DELETE FROM chart_permission_search_key_word_mapping WHERE `from` = 'rddp' AND classify_id = ?"
+	_, err = to.Raw(sql, classifyId).Exec()
+	if err != nil {
+		return
+	}
+
+	if len(permissionIdList) > 0 {
+		chartPermissionSearchKeyWordMappingList := make([]*ChartPermissionSearchKeyWordMapping, 0)
+		for _, permissionId := range permissionIdList {
+			tmpChartPermissionSearchKeyWordMapping := &ChartPermissionSearchKeyWordMapping{
+				ChartPermissionId:  permissionId,
+				KeyWord:            keyword,
+				From:               "rddp",
+				TacticType:         "",
+				TeleconferenceSort: 0,
+				ClassifyId:         classifyId,
+			}
+			chartPermissionSearchKeyWordMappingList = append(chartPermissionSearchKeyWordMappingList, tmpChartPermissionSearchKeyWordMapping)
+		}
+		_, err = to.InsertMulti(len(chartPermissionSearchKeyWordMappingList), chartPermissionSearchKeyWordMappingList)
+	}
 	return
 }
 
@@ -39,10 +88,49 @@ type ChartPermissionMappingIdName struct {
 	PermissionName string
 }
 
-func GetChartPermissionNameFromMappingByKeyword(keyword string, source string) (list []*ChartPermissionMappingIdName, err error) {
+func GetChartPermissionNameFromMappingByKeyword(source string, classifyId int) (list []*ChartPermissionMappingIdName, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := " SELECT b.chart_permission_id AS permission_id,b.permission_name FROM chart_permission_search_key_word_mapping AS a INNER JOIN chart_permission AS b ON a.chart_permission_id = b.chart_permission_id WHERE a.`from` = ? AND a.key_word = ? "
-	_, err = o.Raw(sql, source, keyword).QueryRows(&list)
+	sql := " SELECT b.chart_permission_id AS permission_id,b.permission_name FROM chart_permission_search_key_word_mapping AS a INNER JOIN chart_permission AS b ON a.chart_permission_id = b.chart_permission_id WHERE a.`from` = ? AND a.classify_id = ? "
+	_, err = o.Raw(sql, source, classifyId).QueryRows(&list)
+	return
+}
 
+// UpdateChartPermissionNameFromMappingByKeyword 根据关键词及来源更新新关键词
+func UpdateChartPermissionNameFromMappingByKeyword(newKeyword string, classifyId int, source string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE chart_permission_search_key_word_mapping SET key_word = ? WHERE classify_id = ? AND `from` = ? "
+	_, err = o.Raw(sql, newKeyword, classifyId, source).Exec()
+	return
+}
+
+// ChartPermissionSearchKeyWordMappingAndPermissionName
+// @Description: 分类关联品种
+type ChartPermissionSearchKeyWordMappingAndPermissionName struct {
+	ChartPermissionId   int    `description:"权限id"`
+	ChartPermissionName string `description:"权限名称"`
+	PermissionName      string `description:"权限名称"`
+	KeyWord             string `description:"二级分类名称"`
+	ClassifyId          int    `description:"分类ID"`
+}
+
+// GetPermissionByClassifyId
+// @Description: 根据分类id获取关联的报告权限
+// @author: Roc
+// @datetime 2024-06-19 14:56:44
+// @param classifyId int
+// @return items []*ChartPermissionSearchKeyWordMappingAndPermissionName
+// @return err error
+func GetPermissionByClassifyId(classifyId int) (items []*ChartPermissionSearchKeyWordMappingAndPermissionName, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.chart_permission_name,a.permission_name,b.chart_permission_id,b.key_word,b.classify_id FROM chart_permission AS a 
+ join chart_permission_search_key_word_mapping AS b ON a.chart_permission_id=b.chart_permission_id WHERE b.from='rddp' AND b.classify_id = ? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
 	return
 }
+
+func GetClassifyIdsByPermissionId(chartPermissionIdList []string) (classifyIds []string, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := "SELECT classify_id FROM chart_permission_search_key_word_mapping WHERE `from` =  'rddp' and chart_permission_id IN (" + utils.GetOrmInReplace(len(chartPermissionIdList)) + ") and classify_id <> 0; "
+	_, err = o.Raw(sql, chartPermissionIdList).QueryRows(&classifyIds)
+	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
+}

+ 842 - 113
models/report.go

@@ -1,12 +1,14 @@
 package models
 
 import (
+	"errors"
 	"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"
 )
 
 // 报告状态
@@ -63,6 +65,31 @@ type Report struct {
 	AdminRealName      string    `description:"创建者姓名"`
 	ApproveTime        time.Time `description:"审批时间"`
 	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"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	Pv                  int       `description:"pv"`
+	Uv                  int       `description:"uv"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	CanvasColor         string    `description:"画布颜色"`
+	NeedSplice          int       `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId      int       `description:"版头资源ID"`
+	EndResourceId       int       `description:"版尾资源ID"`
+	ClassifyIdThird     int       `description:"三级分类id"`
+	ClassifyNameThird   string    `description:"三级分类名称"`
+	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	InheritReportId     int       `description:"待继承的报告ID"`
+	VoiceGenerateType   int       `description:"音频生成方式,0:系统生成,1:人工上传"`
 }
 
 type ReportList struct {
@@ -100,12 +127,34 @@ type ReportList struct {
 	OldReportId        int                       `description:"research_report表ID, 大于0则表示该报告为老后台同步过来的"`
 	MsgSendTime        string                    `description:"模版消息发送时间"`
 	CanEdit            bool                      `description:"是否可编辑"`
+	HasAuth            bool                      `description:"是否可操作"`
 	Editor             string                    `description:"编辑人"`
 	AdminId            int                       `description:"创建者账号"`
 	AdminRealName      string                    `description:"创建者姓名"`
 	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"`
+	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	CanvasColor         string    `description:"画布颜色"`
+	NeedSplice          int       `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId      int       `description:"版头资源ID"`
+	EndResourceId       int       `description:"版尾资源ID"`
+	ClassifyIdThird     int       `description:"三级分类id"`
+	ClassifyNameThird   string    `description:"三级分类名称"`
+	InheritReportId     int       `description:"待继承的报告ID"`
+	IsCollect           int       `description:"是否收藏"`
 }
 
 type ReportListResp struct {
@@ -113,17 +162,114 @@ type ReportListResp struct {
 	Paging *paging.PagingItem `description:"分页数据"`
 }
 
-func GetReportListCount(condition string, pars []interface{}, companyType string) (count int, err error) {
-	//产品权限
-	companyTypeSqlStr := ``
-	if companyType == "ficc" {
-		companyTypeSqlStr = " AND classify_id_first != 40 "
-	} else if companyType == "权益" {
-		companyTypeSqlStr = " AND classify_id_first = 40 "
+// GetReportListCountV1
+// @Description: 获取普通报告列表的报告数量
+// @author: Roc
+// @datetime 2024-05-30 15:14:43
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetReportListCountV1(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM report as a WHERE 1=1 `
+	if condition != "" {
+		sql += condition
 	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
 
+// GetReportListV1
+// @Description: 获取普通报告列表的数据
+// @author: Roc
+// @datetime 2024-05-30 15:14:25
+// @param condition string
+// @param pars []interface{}
+// @param startSize int
+// @param pageSize int
+// @return items []*ReportList
+// @return err error
+func GetReportListV1(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT * FROM report as a WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+type ReportPvUv struct {
+	ReportId int
+	PvTotal  int
+	UvTotal  int
+}
+
+func GetReportPvUvByReportIdList(reportIdList []int) (items []ReportPvUv, err error) {
+	num := len(reportIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT report_id, COUNT(1) as pv_total,COUNT(DISTINCT user_id) as uv_total  FROM report_view_record  WHERE report_id in (` + utils.GetOrmInReplace(num) + `) GROUP BY report_id`
+	_, err = o.Raw(sql, reportIdList).QueryRows(&items)
+	return
+}
+
+// GetReportListCountByGrant
+// @Description: 获取共享报告列表的报告数量
+// @author: Roc
+// @datetime 2024-05-30 15:14:01
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetReportListCountByGrant(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.id  FROM report as a 
+    JOIN report_grant b on a.id=b.report_id 
+ WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " GROUP BY a.id "
+
+	sql = `SELECT COUNT(1) AS count  FROM (` + sql + `) d`
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetReportListByGrant
+// @Description: 获取共享报告列表的数据
+// @author: Roc
+// @datetime 2024-05-30 15:15:07
+// @param condition string
+// @param pars []interface{}
+// @param startSize int
+// @param pageSize int
+// @return items []*ReportList
+// @return err error
+func GetReportListByGrant(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT a.* FROM report as a JOIN report_grant b on a.id = b.report_id  WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += ` GROUP BY a.id ORDER BY FIELD(state,3,1,4,5,6,2), a.modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetReportListCount(condition string, pars []interface{}) (count int, err error) {
 	oRddp := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT COUNT(1) AS count  FROM report WHERE 1=1 ` + companyTypeSqlStr
+	sql := `SELECT COUNT(1) AS count  FROM report WHERE 1=1 `
 	if condition != "" {
 		sql += condition
 	}
@@ -131,20 +277,13 @@ func GetReportListCount(condition string, pars []interface{}, companyType string
 	return
 }
 
-func GetReportList(condition string, pars []interface{}, companyType string, startSize, pageSize int) (items []*ReportList, err error) {
+func GetReportList(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	//产品权限
-	companyTypeSqlStr := ``
-	if companyType == "ficc" {
-		companyTypeSqlStr = " AND classify_id_first != 40 "
-	} else if companyType == "权益" {
-		companyTypeSqlStr = " AND classify_id_first = 40 "
-	}
 
 	sql := `SELECT *,
         (SELECT COUNT(1) FROM report_view_record AS rvr WHERE rvr.report_id=report.id) AS pv,
         (SELECT COUNT(DISTINCT user_id) FROM report_view_record AS rvr WHERE rvr.report_id=report.id) AS uv
-        FROM report WHERE 1=1  ` + companyTypeSqlStr
+        FROM report WHERE 1=1  `
 	if condition != "" {
 		sql += condition
 	}
@@ -166,28 +305,15 @@ func PublishReport(reportIds []int) (err error) {
 }
 
 // PublishCancelReport 取消发布报告
-func PublishCancelReport(reportId, state int, publishTimeNullFlag bool) (err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	var sql string
-	if publishTimeNullFlag {
-		sql = ` UPDATE report SET state=?, publish_time=null, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
-	} else {
-		sql = ` UPDATE report SET state=?, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
-	}
-	_, err = o.Raw(sql, state, reportId).Exec()
-	return
-}
-
-// 取消发布报告
-func PublishCancleReport(reportIds int, publishTimeNullFlag bool) (err error) {
+func PublishCancelReport(reportId, state int, publishTimeNullFlag bool, lastModifyAdminId int, lastModifyAdminName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	var sql string
 	if publishTimeNullFlag {
-		sql = ` UPDATE report SET state=1, publish_time=null, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
+		sql = ` UPDATE report SET state=?, publish_time=null, pre_publish_time=null, pre_msg_send=0,last_modify_admin_id=?,last_modify_admin_name=?,modify_time = NOW() WHERE id =?`
 	} else {
-		sql = ` UPDATE report SET state=1, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
+		sql = ` UPDATE report SET state=?, pre_publish_time=null, pre_msg_send=0,last_modify_admin_id=?,last_modify_admin_name=?,modify_time = NOW() WHERE id =?`
 	}
-	_, err = o.Raw(sql, reportIds).Exec()
+	_, err = o.Raw(sql, state, lastModifyAdminId, lastModifyAdminName, reportId).Exec()
 	return
 }
 
@@ -226,7 +352,31 @@ type ReportDetail struct {
 	ThsMsgIsSend       int    `description:"客户群消息是否已发送,0:否,1:是"`
 	HasChapter         int    `description:"是否有章节 0-否 1-是"`
 	ChapterType        string `description:"章节类型 day-晨报 week-周报"`
-	ReportCode         string `description:"报告code"`
+	AdminId            int    `description:"创建者账号"`
+	AdminRealName      string `description:"创建者姓名"`
+	ReportCode         string `description:"报告唯一编码"`
+
+	// eta1.8.3(研报改版)相关内容
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	Pv                  int       `description:"pv"`
+	Uv                  int       `description:"uv"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	HeadStyle           string    `description:"版头样式"`
+	EndStyle            string    `description:"版尾样式"`
+	CanvasColor         string    `description:"画布颜色"`
+	NeedSplice          int       `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId      int       `description:"版头资源ID"`
+	EndResourceId       int       `description:"版尾资源ID"`
+	ClassifyIdThird     int       `description:"三级分类id"`
+	ClassifyNameThird   string    `description:"三级分类名称"`
+	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
 }
 
 func GetReportById(reportId int) (item *ReportDetail, err error) {
@@ -244,39 +394,89 @@ func GetReportByIds(reportIds string) (list []*ReportDetail, err error) {
 }
 
 // GetSimpleReportByIds 根据报告ID查询报告基本信息
-func GetSimpleReportByIds(reportIds []int) (list []*ReportDetail, err error) {
+func GetSimpleReportByIds(reportIds []int) (list []*Report, err error) {
 	if len(reportIds) == 0 {
 		return
 	}
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT id, title FROM report WHERE id IN (` + utils.GetOrmInReplace(len(reportIds)) + `)`
+	sql := `SELECT id, title, report_code FROM report WHERE id IN (` + utils.GetOrmInReplace(len(reportIds)) + `)`
 	_, err = o.Raw(sql, reportIds).QueryRows(&list)
 	return
 }
 
-func GetReportStage(classifyIdFirst, classifyIdSecond int) (count int, err error) {
+// GetReportStage
+// @Description: 获取报告的最大期数(每一年的最大期数)
+// @author: Roc
+// @datetime 2024-06-03 17:44:14
+// @param classifyIdFirst int
+// @param classifyIdSecond int
+// @param classifyIdThird int
+// @return count int
+// @return err error
+func GetReportStage(classifyIdFirst, classifyIdSecond, classifyIdThird int) (count int, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ``
-	if classifyIdSecond > 0 {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_second=? "
-		o.Raw(sql, classifyIdSecond).QueryRow(&count)
+	classifyId := classifyIdThird
+	if classifyId <= 0 {
+		classifyId = classifyIdSecond
+	}
+	if classifyId <= 0 {
+		classifyId = classifyIdFirst
+	}
+	if classifyId <= 0 {
+		err = errors.New("错误的分类id")
+		return
+	}
+	yearStart := time.Date(time.Now().Local().Year(), 1, 1, 0, 0, 0, 0, time.Local)
+
+	sql := `SELECT MAX(stage) AS max_stage FROM report WHERE create_time > ? `
+	if classifyIdThird > 0 {
+		sql += " AND classify_id_third = ? "
+	} else if classifyIdSecond > 0 {
+		sql += " AND classify_id_second = ? "
 	} else {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first=? "
-		o.Raw(sql, classifyIdFirst).QueryRow(&count)
+		sql += " AND classify_id_first = ? "
 	}
+	o.Raw(sql, yearStart, classifyId).QueryRow(&count)
+
 	return
 }
 
-func GetReportStageEdit(classifyIdFirst, classifyIdSecond, reportId int) (count int, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	sql := ``
-	if classifyIdSecond > 0 {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_second=? AND id<>? "
-		o.Raw(sql, classifyIdSecond, reportId).QueryRow(&count)
+// GetReportStageEdit
+// @Description: 获取报告的最大期数(每一年的最大期数)
+// @author: Roc
+// @datetime 2024-06-04 13:50:34
+// @param classifyIdFirst int
+// @param classifyIdSecond int
+// @param classifyIdThird int
+// @param reportId int
+// @return count int
+// @return err error
+func GetReportStageEdit(classifyIdFirst, classifyIdSecond, classifyIdThird, reportId int) (count int, err error) {
+	classifyId := classifyIdThird
+	if classifyId <= 0 {
+		classifyId = classifyIdSecond
+	}
+	if classifyId <= 0 {
+		classifyId = classifyIdFirst
+	}
+	if classifyId <= 0 {
+		err = errors.New("错误的分类id")
+		return
+	}
+	yearStart := time.Date(time.Now().Local().Year(), 1, 1, 0, 0, 0, 0, time.Local)
+
+	sql := `SELECT MAX(stage) AS max_stage FROM report WHERE create_time > ? AND id<>?  `
+	if classifyIdThird > 0 {
+		sql += " AND classify_id_third = ? "
+	} else if classifyIdSecond > 0 {
+		sql += " AND classify_id_second = ? "
 	} else {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first=? AND id<>? "
-		o.Raw(sql, classifyIdFirst, reportId).QueryRow(&count)
+		sql = " AND classify_id_first = ? "
 	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	o.Raw(sql, yearStart, reportId, classifyId).QueryRow(&count)
+
 	return
 }
 
@@ -299,6 +499,8 @@ type AddReq struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdThird    int    `description:"三级分类id"`
+	ClassifyNameThird  string `description:"三级分类名称"`
 	Title              string `description:"标题"`
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
@@ -307,6 +509,18 @@ type AddReq struct {
 	Content            string `description:"内容"`
 	CreateTime         string `description:"创建时间"`
 	ReportVersion      int    `description:"1:旧版,2:新版"`
+	ContentStruct      string `description:"内容组件"`
+	HeadImg            string `description:"报告头图地址"`
+	EndImg             string `description:"报告尾图地址"`
+	CanvasColor        string `description:"画布颜色"`
+	NeedSplice         int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId     int    `description:"版头资源ID"`
+	EndResourceId      int    `description:"版尾资源ID"`
+	CollaborateType    int8   `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish    int8   `description:"是否公开发布,1:是,2:否"`
+	InheritReportId    int    `description:"待继承的报告ID"`
+	GrantAdminIdList   []int  `description:"授权用户id列表"`
 }
 
 type PrePublishReq struct {
@@ -333,6 +547,8 @@ type EditReq struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdThird    int    `description:"三级分类id"`
+	ClassifyNameThird  string `description:"三级分类名称"`
 	Title              string `description:"标题"`
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
@@ -340,6 +556,17 @@ type EditReq struct {
 	State              int    `description:"状态:1:未发布,2:已发布"`
 	Content            string `description:"内容"`
 	CreateTime         string `description:"创建时间"`
+	ContentStruct      string `description:"内容组件"`
+	HeadImg            string `description:"报告头图地址"`
+	EndImg             string `description:"报告尾图地址"`
+	CanvasColor        string `description:"画布颜色"`
+	NeedSplice         int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId     int    `description:"版头资源ID"`
+	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列表"`
 }
 
 type EditResp struct {
@@ -371,6 +598,12 @@ func EditReport(item *Report, reportId int64) (err error) {
 	return
 }
 
+func (m *Report) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
 type ReportDetailReq struct {
 	ReportId int `description:"报告id"`
 }
@@ -397,6 +630,13 @@ type SendTemplateMsgReq struct {
 	ReportId int `description:"报告id"`
 }
 
+// SendTemplateMsgResp
+// @Description: 报告推送返回结构体
+type SendTemplateMsgResp struct {
+	ReportId    int    `description:"报告id"`
+	MsgSendTime string `description:"报告推送时间"`
+}
+
 func ModifyReportMsgIsSend(reportId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	report, err := GetReportById(reportId)
@@ -452,6 +692,15 @@ type SaveReportContent struct {
 	Content  string `description:"内容"`
 	ReportId int    `description:"报告id"`
 	NoChange int    `description:"内容是否未改变:1:内容未改变"`
+
+	// 以下是智能研报相关
+	ContentStruct  string `description:"内容组件"`
+	HeadImg        string `description:"报告头图地址"`
+	EndImg         string `description:"报告尾图地址"`
+	CanvasColor    string `description:"画布颜色"`
+	NeedSplice     int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId int    `description:"版头资源ID"`
+	EndResourceId  int    `description:"版尾资源ID"`
 }
 
 func EditReportContent(reportId int, content, contentSub string) (err error) {
@@ -461,16 +710,16 @@ func EditReportContent(reportId int, content, contentSub string) (err error) {
 	return
 }
 
-func AddReportSaveLog(reportId, adminId int, content, contentSub, adminName string) (err error) {
+func AddReportSaveLog(reportId, adminId int, content, contentSub, contentStruct, canvasColor, adminName string, headResourceId, endResourceId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ` INSERT INTO report_save_log(report_id, content,content_sub,admin_id,admin_name) VALUES (?,?,?,?,?) `
-	_, err = o.Raw(sql, reportId, content, contentSub, adminId, adminName).Exec()
+	sql := ` INSERT INTO report_save_log(report_id, content,content_sub,content_struct,canvas_color,head_resource_id,end_resource_id,admin_id,admin_name) VALUES (?,?,?,?,?,?,?,?,?) `
+	_, err = o.Raw(sql, reportId, content, contentSub, contentStruct, canvasColor, headResourceId, endResourceId, adminId, adminName).Exec()
 	return
 }
 
 func MultiAddReportChaptersSaveLog(items []*ReportChapter, adminId int, adminRealName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	p, err := o.Raw(`INSERT INTO report_save_log(report_id, report_chapter_id, content, content_sub, admin_id, admin_name) VALUES (?,?,?,?,?,?)`).Prepare()
+	p, err := o.Raw(`INSERT INTO report_save_log(report_id, report_chapter_id, content, content_sub,content_struct,admin_id, admin_name) VALUES (?,?,?,?,?,?,?)`).Prepare()
 	if err != nil {
 		return
 	}
@@ -478,7 +727,7 @@ func MultiAddReportChaptersSaveLog(items []*ReportChapter, adminId int, adminRea
 		_ = p.Close()
 	}()
 	for _, v := range items {
-		_, err = p.Exec(v.ReportId, v.ReportChapterId, v.Content, v.ContentSub, adminId, adminRealName)
+		_, err = p.Exec(v.ReportId, v.ReportChapterId, v.Content, v.ContentSub, v.ContentStruct, adminId, adminRealName)
 		if err != nil {
 			return
 		}
@@ -532,38 +781,6 @@ func GetDayWeekReportStage(classifyIdFirst int, yearStart time.Time) (count int,
 	return
 }
 
-// AddReportAndChapter 新增报告及章节
-func AddReportAndChapter(reportItem *Report, chapterItemList []*ReportChapter) (reportId int64, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	to, err := o.Begin()
-	if err != nil {
-		return
-	}
-	defer func() {
-		if err != nil {
-			_ = to.Rollback()
-		} else {
-			_ = to.Commit()
-		}
-	}()
-
-	if reportId, err = to.Insert(reportItem); err != nil {
-		return
-	}
-	if len(chapterItemList) > 0 {
-		for _, chapterItem := range chapterItemList {
-			chapterItem.ReportId = int(reportId)
-			cpId, tmpErr := to.Insert(chapterItem)
-			if tmpErr != nil {
-				return
-			}
-			chapterItem.ReportChapterId = int(cpId)
-		}
-	}
-
-	return
-}
-
 // GetReportByReportId 主键获取报告
 func GetReportByReportId(reportId int) (item *Report, err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -615,10 +832,27 @@ func (reportInfo *Report) UpdateReport(cols []string) (err error) {
 	return
 }
 
-// 晨周报详情
+// ReportDetailView
+// @Description: 晨周报详情
 type ReportDetailView struct {
 	*ReportDetail
-	ChapterList []*ReportChapter
+	ChapterList    []*ReportChapter
+	GrandAdminList []ReportDetailViewAdmin
+	PermissionList []ReportDetailViewPermission
+}
+
+// ReportDetailViewAdmin
+// @Description: 报告里面的授权人
+type ReportDetailViewAdmin struct {
+	AdminId   int
+	AdminName string
+}
+
+// ReportDetailViewPermission
+// @Description: 报告分类关联的品种权限
+type ReportDetailViewPermission struct {
+	PermissionId   int
+	PermissionName string
 }
 
 func GetUnPublishDayReport(startTime time.Time, endTime time.Time) (item *Report, err error) {
@@ -654,6 +888,8 @@ type ElasticReportDetail struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类ID"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyId         int    `description:"最小单元的分类ID"`
+	ClassifyName       string `description:"最小单元的分类名称"`
 	Categories         string `description:"关联的品种名称(包括品种别名)"`
 	StageStr           string `description:"报告期数"`
 }
@@ -677,7 +913,7 @@ func GetNewReportExist(oldReportId int) (item *Report, err error) {
 }
 
 // PublishReportAndChapter 发布报告及章节
-func PublishReportAndChapter(reportInfo *Report, publishIds, unPublishIds []int, isPublishReport bool, cols []string) (err error) {
+func PublishReportAndChapter(reportInfo *Report, isPublishReport bool, cols []string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
@@ -692,19 +928,27 @@ func PublishReportAndChapter(reportInfo *Report, publishIds, unPublishIds []int,
 	}()
 	// 更新报告
 	if isPublishReport {
-		if _, err = to.Update(reportInfo, cols...); err != nil {
+		_, err = to.Update(reportInfo, cols...)
+		if err != nil {
 			return
 		}
 	}
+
+	// 发布该报告的所有章节
+	sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ?  `
+	_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id).Exec()
+
 	// 发布章节
-	if len(publishIds) > 0 {
-		sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(publishIds)) + `) `
-		_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id, publishIds).Exec()
-	}
-	if len(unPublishIds) > 0 {
-		sql := ` UPDATE report_chapter SET publish_state = 1, publish_time = NULL, is_edit = 0 WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(unPublishIds)) + `) `
-		_, err = to.Raw(sql, reportInfo.Id, unPublishIds).Exec()
-	}
+	//if len(publishIds) > 0 {
+	//	sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(publishIds)) + `) `
+	//	_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id, publishIds).Exec()
+	//}
+
+	//if len(unPublishIds) > 0 {
+	//	sql := ` UPDATE report_chapter SET publish_state = 1, publish_time = NULL, is_edit = 0 WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(unPublishIds)) + `) `
+	//	_, err = to.Raw(sql, reportInfo.Id, unPublishIds).Exec()
+	//}
+
 	return
 }
 
@@ -718,18 +962,18 @@ SELECT DISTINCT report_id FROM report_chapter WHERE publish_state = 2 AND (video
 }
 
 // PublishReportById 发布报告
-func PublishReportById(reportId int, publishTime time.Time) (err error) {
+func PublishReportById(reportId int, publishTime time.Time, lastModifyAdminId int, lastModifyAdminName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `UPDATE report SET state = 2, publish_time = ?, pre_publish_time=null, pre_msg_send=0, modify_time = NOW() WHERE id = ? `
-	_, err = o.Raw(sql, publishTime, reportId).Exec()
+	sql := `UPDATE report SET state = 2, publish_time = ?, pre_publish_time=null, pre_msg_send=0, modify_time = NOW(),last_modify_admin_id=?,last_modify_admin_name=? WHERE id = ? `
+	_, err = o.Raw(sql, publishTime, lastModifyAdminId, lastModifyAdminName, reportId).Exec()
 	return
 }
 
 // ResetReportById 重置报告状态
-func ResetReportById(reportId, state int) (err error) {
+func ResetReportById(reportId, state int, lastModifyAdminId int, lastModifyAdminName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `UPDATE report SET state = ?, pre_publish_time = null, pre_msg_send = 0, modify_time = NOW() WHERE id = ?`
-	_, err = o.Raw(sql, state, reportId).Exec()
+	sql := `UPDATE report SET state = ?, pre_publish_time = null, pre_msg_send = 0, modify_time = NOW(),last_modify_admin_id=?,last_modify_admin_name=? WHERE id = ?`
+	_, err = o.Raw(sql, state, lastModifyAdminId, lastModifyAdminName, reportId).Exec()
 	return
 }
 
@@ -984,6 +1228,14 @@ func UpdateReportFirstClassifyNameByClassifyId(classifyId int, classifyName stri
 	return
 }
 
+// UpdateReportThirdClassifyNameByClassifyId 更新报告的三级分类名称字段
+func UpdateReportThirdClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE report SET classify_name_third = ? WHERE classify_id_third = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
 // UpdateReportSecondClassifyFirstNameByClassifyId 更新报告二级分类的一级分类名称和id
 func UpdateReportSecondClassifyFirstNameByClassifyId(classifyId, newClassifyId int, classifyName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -1047,8 +1299,9 @@ func UpdateReportChapterPublishTime(reportId int, videoNameDate string) (err err
 
 // MarkEditReport 标记编辑英文研报的请求数据
 type MarkEditReport struct {
-	ReportId int `description:"研报id"`
-	Status   int `description:"标记状态,1:编辑中,2:编辑完成"`
+	ReportId        int `description:"研报id"`
+	ReportChapterId int `description:"研报章节id"`
+	Status          int `description:"标记状态,1:编辑中,2:查询状态,3:编辑完成"`
 }
 
 type MarkReportResp struct {
@@ -1128,7 +1381,7 @@ func GetReportStateCount(state int) (count int, err error) {
 }
 
 // UpdateReportsStateByCond 批量更新报告状态
-func UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, newState int) (err error) {
+func UpdateReportsStateByCond(classifyFirstId, classifySecondId, classifyThirdId, oldState, newState int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	cond := ``
 	if classifyFirstId > 0 {
@@ -1137,6 +1390,9 @@ func UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, newSt
 	if classifySecondId > 0 {
 		cond += fmt.Sprintf(` AND classify_id_second = %d`, classifySecondId)
 	}
+	if classifyThirdId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_third = %d`, classifyThirdId)
+	}
 	sql := fmt.Sprintf(`UPDATE report SET state = ?, pre_publish_time = NULL WHERE state = ? %s`, cond)
 	_, err = o.Raw(sql, newState, oldState).Exec()
 	return
@@ -1160,6 +1416,13 @@ func UpdateReportsStateBySecondIds(oldState, newState int, secondIds []int) (err
 	return
 }
 
+// GetReportPdfUrlReq 获取报告pdf地址请求体
+type GetReportPdfUrlReq struct {
+	ReportUrl  string `description:"报告Url"`
+	ReportCode string `description:"报告Code"`
+	Type       int    `description:"类型 1-pdf 2-图片"`
+}
+
 func ModifyReportPdfUrl(reportId int, detailPdfUrl string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `UPDATE report SET detail_pdf_url=? WHERE id=? `
@@ -1174,6 +1437,19 @@ 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) {
@@ -1181,4 +1457,457 @@ func UpdatePdfUrlReportById(reportId int) (err error) {
 	sql := `UPDATE report SET detail_img_url = '',detail_pdf_url='',modify_time=NOW() WHERE id = ? `
 	_, err = o.Raw(sql, reportId).Exec()
 	return
-}
+}
+
+// InsertMultiReport
+// @Description: 批量新增报告
+// @author: Roc
+// @datetime 2024-06-27 15:55:25
+// @param items []*Report
+// @return err error
+func InsertMultiReport(items []*Report) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(500, items)
+
+	return
+}
+
+func GetReportListByCollectCount(classifyIdFirst, classifyIdSecond, classifyIdThird []string, keyword, author string, state int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var params []interface{}
+
+	// SQL 主体
+	sql := `
+		SELECT COUNT(DISTINCT t.id) AS totalCount
+		FROM (
+			SELECT b.id
+			FROM report AS b
+			LEFT JOIN user_collect_classify AS a ON a.classify_id = b.classify_id_first
+			WHERE 1 = 1
+	`
+
+	// 处理 classifyIdFirst
+	if len(classifyIdFirst) > 0 {
+		sql += " AND a.classify_id IN ( " + utils.GetOrmInReplace(len(classifyIdFirst)) + " ) "
+		for _, id := range classifyIdFirst {
+			params = append(params, id)
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if state > 0 {
+		sql += " AND b.state = ? "
+		params = append(params, state)
+	}
+
+	// 第二段 SQL
+	sql += `
+		UNION ALL
+		SELECT b.id
+		FROM report AS b
+		LEFT JOIN user_collect_classify AS a ON a.classify_id = b.classify_id_second
+		WHERE 1 = 1
+	`
+
+	// 处理 classifyIdSecond
+	if len(classifyIdSecond) > 0 {
+		sql += " AND a.classify_id IN ( " + utils.GetOrmInReplace(len(classifyIdSecond)) + " ) "
+		for _, id := range classifyIdSecond {
+			params = append(params, id)
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if state > 0 {
+		sql += " AND b.state = ? "
+		params = append(params, state)
+	}
+
+	// 第三段 SQL
+	sql += `
+		UNION ALL
+		SELECT b.id
+		FROM report AS b
+		LEFT JOIN user_collect_classify AS a ON a.classify_id = b.classify_id_third
+		WHERE 1 = 1
+	`
+
+	// 处理 classifyIdThird
+	if len(classifyIdThird) > 0 {
+		sql += " AND a.classify_id IN ( " + utils.GetOrmInReplace(len(classifyIdThird)) + " ) "
+		for _, id := range classifyIdThird {
+			params = append(params, id)
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if state > 0 {
+		sql += " AND b.state = ? "
+		params = append(params, state)
+	}
+
+	sql += ") AS t"
+
+	// 执行 SQL 查询获取总数
+	err = o.Raw(sql, params...).QueryRow(&count)
+	return count, err
+}
+
+func GetReportListByCollectList(classifyIdFirst, classifyIdSecond, classifyIdThird []string, keyword, orderField, orderType string, startSize, pageSize int, author string, state int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var params []interface{}
+
+	// SQL 主体
+	sql := `
+		SELECT DISTINCT t.id, t.title, t.author, t.modify_time, t.publish_time
+		FROM (
+			SELECT b.id, b.title, b.author, b.modify_time, b.publish_time
+			FROM report AS b
+			LEFT JOIN user_collect_classify AS a ON a.classify_id = b.classify_id_first
+			WHERE 1 = 1
+	`
+
+	// 处理 classifyIdFirst
+	if len(classifyIdFirst) > 0 {
+		sql += " AND a.classify_id IN ( " + utils.GetOrmInReplace(len(classifyIdFirst)) + " ) "
+		for _, id := range classifyIdFirst {
+			params = append(params, id)
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if state > 0 {
+		sql += " AND b.state = ? "
+		params = append(params, state)
+	}
+
+	// 第二段 SQL
+	sql += `
+		UNION ALL
+		SELECT b.id, b.title, b.author, b.modify_time, b.publish_time
+		FROM report AS b
+		LEFT JOIN user_collect_classify AS a ON a.classify_id = b.classify_id_second
+		WHERE 1 = 1
+	`
+
+	// 处理 classifyIdSecond
+	if len(classifyIdSecond) > 0 {
+		sql += " AND a.classify_id IN ( " + utils.GetOrmInReplace(len(classifyIdSecond)) + " ) "
+		for _, id := range classifyIdSecond {
+			params = append(params, id)
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if state > 0 {
+		sql += " AND b.state = ? "
+		params = append(params, state)
+	}
+
+	// 第三段 SQL
+	sql += `
+		UNION ALL
+		SELECT b.id, b.title, b.author, b.modify_time, b.publish_time
+		FROM report AS b
+		LEFT JOIN user_collect_classify AS a ON a.classify_id = b.classify_id_third
+		WHERE 1 = 1
+	`
+
+	// 处理 classifyIdThird
+	if len(classifyIdThird) > 0 {
+		sql += " AND a.classify_id IN ( " + utils.GetOrmInReplace(len(classifyIdThird)) + " ) "
+		for _, id := range classifyIdThird {
+			params = append(params, id)
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if state > 0 {
+		sql += " AND b.state = ? "
+		params = append(params, state)
+	}
+
+	sql += ") AS t "
+
+	// 排序处理
+	if orderField != "" && orderType != "" {
+		sql += " ORDER BY " + orderField + " " + orderType
+	} else {
+		sql += " ORDER BY t.modify_time DESC, t.publish_time DESC "
+	}
+
+	// 分页
+	sql += " LIMIT ?, ?"
+	params = append(params, (startSize-1)*pageSize, pageSize)
+
+	// 执行 SQL 查询
+	_, err = o.Raw(sql, params...).QueryRows(&items)
+	return items, err
+}
+
+func GetReportListByCollectCountV2(classifyIdFirst, classifyIdSecond, classifyIdThird, chartPermissionIdList []string, keyword, author string, isPublic int, stateArr []int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var params []interface{}
+
+	// SQL 主体
+	sql := `
+		SELECT COUNT(DISTINCT t.id) AS totalCount
+		FROM (
+			SELECT b.id
+			FROM report AS b WHERE 1 = 1
+	`
+	if len(chartPermissionIdList) > 0 {
+		classifyIds, e := GetClassifyIdsByPermissionId(chartPermissionIdList)
+		if e != nil {
+			err = e
+			return
+		}
+
+		if len(classifyIds) > 0 {
+			sql += ` AND ( (b.classify_id_first IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) AND b.classify_id_second = 0) 
+			OR (b.classify_id_second IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) AND b.classify_id_third = 0) 
+			OR b.classify_id_third IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) )`
+			for _, classifyId := range classifyIds {
+				params = append(params, classifyId)
+			}
+			for _, classifyId := range classifyIds {
+				params = append(params, classifyId)
+			}
+			for _, classifyId := range classifyIds {
+				params = append(params, classifyId)
+			}
+		}
+	}
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if isPublic > 0 {
+		sql += " AND b.is_public_publish = ? "
+		params = append(params, isPublic)
+	}
+
+	if len(stateArr) > 0 {
+		sql += fmt.Sprintf(" AND b.state IN (%s)", utils.GetOrmInReplace(len(stateArr)))
+		params = append(params, stateArr)
+	}
+
+	// 分类id判断
+	classifyIdSqlList := make([]string, 0)
+	// 处理 classifyIdFirst
+	if len(classifyIdFirst) > 0 {
+		classifyIdSqlList = append(classifyIdSqlList, " ( b.classify_id_first IN ( "+utils.GetOrmInReplace(len(classifyIdFirst))+" ) AND classify_id_second = 0 AND classify_id_third = 0 ) ")
+		for _, id := range classifyIdFirst {
+			params = append(params, id)
+		}
+	}
+	// 处理 classifyIdSecond
+	if len(classifyIdSecond) > 0 {
+		classifyIdSqlList = append(classifyIdSqlList, " ( b.classify_id_second IN ( "+utils.GetOrmInReplace(len(classifyIdSecond))+" ) AND classify_id_third = 0) ")
+		for _, id := range classifyIdSecond {
+			params = append(params, id)
+		}
+	}
+
+	// 处理 classifyIdThird
+	if len(classifyIdThird) > 0 {
+		classifyIdSqlList = append(classifyIdSqlList, " ( b.classify_id_third IN ( "+utils.GetOrmInReplace(len(classifyIdThird))+" ) ) ")
+		for _, id := range classifyIdThird {
+			params = append(params, id)
+		}
+	}
+
+	if len(classifyIdSqlList) > 0 {
+		sql += ` AND (` + strings.Join(classifyIdSqlList, " OR ") + `) `
+	}
+
+	sql += ") AS t"
+
+	// 执行 SQL 查询获取总数
+	err = o.Raw(sql, params...).QueryRow(&count)
+	return count, err
+}
+
+func GetReportListByCollectListV2(classifyIdFirst, classifyIdSecond, classifyIdThird, chartPermissionIdList []string, keyword, orderField, orderType string, startSize, pageSize int, author string, isPublic int, stateArr []int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var params []interface{}
+
+	// SQL 主体
+	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 
+		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 
+			FROM report AS b WHERE 1 = 1
+	`
+
+	// 处理关键词
+	if keyword != "" {
+		sql += " AND (b.title LIKE ? OR b.admin_real_name LIKE ?)"
+		params = append(params, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if author != "" {
+		sql += " AND b.author = ? "
+		params = append(params, author)
+	}
+
+	if isPublic > 0 {
+		sql += " AND b.is_public_publish = ? "
+		params = append(params, isPublic)
+	}
+
+	if len(stateArr) > 0 {
+		sql += fmt.Sprintf(" AND b.state IN (%s)", utils.GetOrmInReplace(len(stateArr)))
+		params = append(params, stateArr)
+	}
+
+	// 分类id判断
+	classifyIdSqlList := make([]string, 0)
+	// 处理 classifyIdFirst
+	if len(classifyIdFirst) > 0 {
+		classifyIdSqlList = append(classifyIdSqlList, " ( b.classify_id_first IN ( "+utils.GetOrmInReplace(len(classifyIdFirst))+" ) AND classify_id_second = 0 AND classify_id_third = 0 ) ")
+		for _, id := range classifyIdFirst {
+			params = append(params, id)
+		}
+	}
+	// 处理 classifyIdSecond
+	if len(classifyIdSecond) > 0 {
+		classifyIdSqlList = append(classifyIdSqlList, " ( b.classify_id_second IN ( "+utils.GetOrmInReplace(len(classifyIdSecond))+" ) AND classify_id_third = 0) ")
+		for _, id := range classifyIdSecond {
+			params = append(params, id)
+		}
+	}
+
+	// 处理 classifyIdThird
+	if len(classifyIdThird) > 0 {
+		classifyIdSqlList = append(classifyIdSqlList, " ( b.classify_id_third IN ( "+utils.GetOrmInReplace(len(classifyIdThird))+" ) ) ")
+		for _, id := range classifyIdThird {
+			params = append(params, id)
+		}
+	}
+
+	if len(classifyIdSqlList) > 0 {
+		sql += ` AND (` + strings.Join(classifyIdSqlList, " OR ") + `) `
+	}
+
+	if len(chartPermissionIdList) > 0 {
+		classifyIds, e := GetClassifyIdsByPermissionId(chartPermissionIdList)
+		if e != nil {
+			err = e
+			return nil, err
+		}
+
+		if len(classifyIds) > 0 {
+			sql += ` AND ( (b.classify_id_first IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) AND b.classify_id_second = 0) 
+			OR (b.classify_id_second IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) AND b.classify_id_third = 0) 
+			OR b.classify_id_third IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) )`
+			for _, classifyId := range classifyIds {
+				params = append(params, classifyId)
+			}
+			for _, classifyId := range classifyIds {
+				params = append(params, classifyId)
+			}
+			for _, classifyId := range classifyIds {
+				params = append(params, classifyId)
+			}
+		}
+	}
+
+	sql += ") AS t "
+
+	// 排序处理
+	if orderField != "" && orderType != "" {
+		sql += " ORDER BY " + orderField + " " + orderType
+	} else {
+		sql += " ORDER BY t.modify_time DESC, t.publish_time DESC "
+	}
+
+	// 分页
+	sql += " LIMIT ?, ?"
+	params = append(params, (startSize-1)*pageSize, pageSize)
+
+	// 执行 SQL 查询
+	_, 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"`
+}

+ 110 - 0
models/report/report_chapter_grant.go

@@ -0,0 +1,110 @@
+package report
+
+import (
+	"eta/eta_mobile/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportChapterGrant
+// @Description: 报告章节授权用户表
+type ReportChapterGrant struct {
+	GrantId         int       `orm:"column(grant_id)"` // 授权id
+	ReportChapterId int       `description:"报告章节id"`
+	AdminId         int       `description:"授权的用户id"`
+	CreateTime      time.Time `description:"授权时间"`
+}
+
+// MultiAddReportChapterGrantGrant
+// @Description: 批量添加报告授权用户
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:36:02
+// @param reportChapterId int
+// @param list []*ReportChapterGrant
+// @return err error
+func (m ReportChapterGrant) MultiAddReportChapterGrantGrant(reportChapterId int, list []*ReportChapterGrant) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_chapter_grant where report_chapter_id=?"
+	_, err = to.Raw(sql, reportChapterId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增授权记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(500, list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// GetGrantListById
+// @Description: 根据id获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterId int
+// @return list []*ReportChapterGrant
+// @return err error
+func (m ReportChapterGrant) GetGrantListById(reportChapterId int) (list []*ReportChapterGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_grant WHERE report_chapter_id=? `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// GetGrantListByIdList
+// @Description: 根据id列表获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterIdList []int
+// @return list []*ReportChapterGrant
+// @return err error
+func (m ReportChapterGrant) GetGrantListByIdList(reportChapterIdList []int) (list []*ReportChapterGrant, err error) {
+	num := len(reportChapterIdList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_grant WHERE report_chapter_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, reportChapterIdList).QueryRows(&list)
+
+	return
+}
+
+// GetGrantByIdAndAdmin
+// @Description: 根据reportId和操作人获取报告章节权限配置
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:49:59
+// @param reportChapterId int
+// @param sysUserId int
+// @return item *ReportGrant
+// @return err error
+func (m ReportChapterGrant) GetGrantByIdAndAdmin(reportChapterId, sysUserId int) (item *ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_grant WHERE report_chapter_id = ? AND admin_id = ? `
+	err = o.Raw(sql, reportChapterId, sysUserId).QueryRow(&item)
+
+	return
+}

+ 152 - 0
models/report/report_chapter_permission_mapping.go

@@ -0,0 +1,152 @@
+package report
+
+import (
+	"eta/eta_mobile/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportChapterPermissionMapping
+// @Description: 报告章节的权限关系表
+type ReportChapterPermissionMapping struct {
+	ReportChapterPermissionMappingId int `orm:"column(report_chapter_permission_mapping_id)"`
+	ReportChapterId                  int `description:"报告章节的id"` // 报告章节的id
+	ChartPermissionId                int `description:"权限id"`    // 权限id
+	CreateTime                       time.Time
+}
+
+// MultiAddReportChapterPermissionMappingPermission
+// @Description: 批量添加报告品种权限用户
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:36:02
+// @param reportChapterId int
+// @param list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) MultiAddReportChapterPermissionMappingPermission(reportChapterId int, list []*ReportChapterPermissionMapping) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_chapter_permission_mapping where report_chapter_id=?"
+	_, err = to.Raw(sql, reportChapterId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增品种权限记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(500, list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// GetPermissionListById
+// @Description: 根据id获取品种权限列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterId int
+// @return list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) GetPermissionListById(reportChapterId int) (list []*ReportChapterPermissionMapping, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_permission_mapping WHERE report_chapter_id=? `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// ReportChapterPermissionItem
+// @Description: 报告章节的权限关系表(带有品种名称)
+type ReportChapterPermissionItem struct {
+	ReportChapterPermissionMappingId int    `orm:"column(report_chapter_permission_mapping_id)"`
+	ReportChapterId                  int    `description:"报告章节的id"`
+	ChartPermissionId                int    `description:"权限id"`
+	ChartPermissionName              string `description:"品种名称"`
+	CreateTime                       time.Time
+}
+
+// GetPermissionItemListById
+// @Description: 根据id获取品种权限列表(带有品种名称)
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterId int
+// @return list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) GetPermissionItemListById(reportChapterId int) (list []*ReportChapterPermissionItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.*,b.chart_permission_name FROM report_chapter_permission_mapping AS a 
+         JOIN chart_permission AS b on a.chart_permission_id=b.chart_permission_id WHERE report_chapter_id=? `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// GetPermissionListByIdList
+// @Description: 根据id列表获取品种权限列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterIdList []int
+// @return list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) GetPermissionListByIdList(reportChapterIdList []int) (list []*ReportChapterPermissionMapping, err error) {
+	num := len(reportChapterIdList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_permission_mapping WHERE report_chapter_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, reportChapterIdList).QueryRows(&list)
+
+	return
+}
+
+// MultiAdd
+// @Description: 批量添加
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-20 18:04:33
+// @param list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) MultiAdd(list []*ReportChapterPermissionMapping) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 新增品种权限记录
+	if len(list) > 0 {
+		_, err = to.InsertMulti(500, list)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 126 - 0
models/report/report_grant.go

@@ -0,0 +1,126 @@
+package report
+
+import (
+	"eta/eta_mobile/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportGrant
+// @Description: 报告授权用户表
+type ReportGrant struct {
+	GrantId    int       `orm:"column(grant_id)"` // 授权id
+	ReportId   int       `description:"报告id"`
+	AdminId    int       `description:"授权的用户id"`
+	CreateTime time.Time `description:"授权时间"`
+}
+
+// MultiAddReportGrantGrant
+// @Description: 批量添加报告授权用户
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:36:02
+// @param reportId int
+// @param list []*ReportGrant
+// @return err error
+func (m ReportGrant) MultiAddReportGrantGrant(reportId int, list []*ReportGrant) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_grant where report_id=?"
+	_, err = to.Raw(sql, reportId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增授权记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(500, list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// GetGrantListById
+// @Description: 根据id获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportId int
+// @return list []*ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantListById(reportId int) (list []*ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id=? `
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// GetGrantListByIdList
+// @Description: 根据id列表获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportIdList []int
+// @return list []*ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantListByIdList(reportIdList []int) (list []*ReportGrant, err error) {
+	num := len(reportIdList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, reportIdList).QueryRows(&list)
+
+	return
+}
+
+// GetGrantByIdAndAdmin
+// @Description: 根据reportId和操作人获取报告权限配置
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:49:59
+// @param reportId int
+// @param sysUserId int
+// @return item *ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantByIdAndAdmin(reportId, sysUserId int) (item *ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id = ? AND admin_id = ? `
+	err = o.Raw(sql, reportId, sysUserId).QueryRow(&item)
+
+	return
+}
+
+// GetGrantListByAdminId
+// @Description: 根据id获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param adminId int
+// @return list []*ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantListByAdminId(adminId int) (list []*ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE admin_id=? `
+	_, err = o.Raw(sql, adminId).QueryRows(&list)
+
+	return
+}

+ 13 - 4
models/report_approve/report_approve.go

@@ -17,6 +17,7 @@ type ReportApprove struct {
 	ReportTitle      string    `description:"报告标题"`
 	ClassifyFirstId  int       `description:"一级分类ID"`
 	ClassifySecondId int       `description:"二级分类ID"`
+	ClassifyThirdId  int       `description:"三级分类ID"`
 	State            int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
 	FlowId           int       `description:"审批流ID"`
 	FlowVersion      int       `description:"审批流版本"`
@@ -37,6 +38,7 @@ var ReportApproveCols = struct {
 	ReportTitle      string `description:"报告标题"`
 	ClassifyFirstId  string `description:"一级分类ID"`
 	ClassifySecondId string `description:"二级分类ID"`
+	ClassifyThirdId  string `description:"三级分类ID"`
 	State            string
 	FlowId           string
 	FlowVersion      string
@@ -55,6 +57,7 @@ var ReportApproveCols = struct {
 	ReportTitle:      "report_title",
 	ClassifyFirstId:  "classify_first_id",
 	ClassifySecondId: "classify_second_id",
+	ClassifyThirdId:  "classify_third_id",
 	State:            "state",
 	FlowId:           "flow_id",
 	FlowVersion:      "flow_version",
@@ -258,6 +261,7 @@ type ReportApproveItemOrm struct {
 	ReportTitle           string    `description:"报告标题"`
 	ClassifyFirstId       int       `description:"一级分类ID"`
 	ClassifySecondId      int       `description:"二级分类ID"`
+	ClassifyThirdId       int       `description:"二级分类ID"`
 	State                 int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
 	RecordState           int       `description:"审批记录状态:1-待审批;2-已通过;3-已驳回"`
 	FlowId                int       `description:"审批流ID"`
@@ -271,6 +275,8 @@ type ReportApproveItemOrm struct {
 	HandleTime            time.Time `description:"处理时间"`
 	CreateTime            time.Time `description:"创建时间"`
 	ModifyTime            time.Time `description:"修改时间"`
+	NodeState             int       `description:"当前节点审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回" json:"-"`
+	NodeApproveTime       time.Time `description:"当前节点审批时间" json:"-"`
 }
 
 // GetApprovingReportApproveCount 获取待处理的审批分页列表总数
@@ -278,7 +284,7 @@ func GetApprovingReportApproveCount(cond string, pars []interface{}) (count int,
 	o := orm.NewOrmUsingDB("rddp")
 	base := fmt.Sprintf(`SELECT a.report_approve_record_id
 		FROM report_approve_record AS a
-		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id
+		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id AND a.node_id = b.curr_node_id
 		WHERE 1 = 1 %s`, cond)
 	sql := fmt.Sprintf(`SELECT COUNT(1) FROM (%s) t`, base)
 	err = o.Raw(sql, pars).QueryRow(&count)
@@ -294,7 +300,7 @@ func GetApprovingReportApprovePageList(cond string, pars []interface{}, orderRul
 	}
 	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.state AS record_state, b.*
 		FROM report_approve_record AS a
-		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id
+		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id AND a.node_id = b.curr_node_id
 		WHERE 1 = 1 %s %s
 		LIMIT ?,?`, cond, order)
 	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
@@ -320,7 +326,7 @@ func GetApprovedReportApprovePageList(cond string, pars []interface{}, orderRule
 	if orderRule != "" {
 		order = ` ORDER BY ` + orderRule
 	}
-	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.state AS record_state, a.approve_time AS handle_time, b.*
+	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.node_state AS record_state,a.node_state,a.node_approve_time, a.approve_time AS handle_time, b.*
 		FROM report_approve_record AS a
 		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id
 		WHERE 1 = 1 %s %s
@@ -378,6 +384,7 @@ type ReportApproveDetailReport struct {
 	ReportType     int    `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ReportId       int    `description:"报告ID"`
 	ReportTitle    string `description:"报告标题"`
+	ReportCode     string `description:"报告code"`
 	ReportClassify string `description:"报告分类"`
 	//ClassifyFirstId  int    `description:"一级分类ID"`
 	//ClassifySecondId int    `description:"二级分类ID"`
@@ -426,7 +433,8 @@ func (m *ReportApprove) CreateApproveAndRecord(approveItem *ReportApprove, recor
 
 // ReportApprovePassReq 审批通过请求体
 type ReportApprovePassReq struct {
-	ReportApproveId int `description:"审批ID"`
+	ReportApproveId int    `description:"审批ID"`
+	ReportUrl       string `description:"报告URL"`
 }
 
 // ReportApproveRefuseReq 审批驳回请求体
@@ -445,4 +453,5 @@ type ReportApproveCheckApproveOpenReq struct {
 	ReportType       int `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId  int `description:"一级分类ID"`
 	ClassifySecondId int `description:"二级分类ID"`
+	ClassifyThirdId  int `description:"三级分类ID"`
 }

+ 12 - 1
models/report_approve/report_approve_flow.go

@@ -16,9 +16,11 @@ type ReportApproveFlow struct {
 	ReportType          int       `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId     int       `description:"一级分类ID"`
 	ClassifySecondId    int       `description:"二级分类ID"`
+	ClassifyThirdId     int       `description:"三级分类ID"`
 	CurrVersion         int       `description:"当前版本号"`
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
+	Enabled             int       `description:"1:有效,0:禁用"`
 }
 
 var ReportApproveFlowCols = struct {
@@ -27,18 +29,22 @@ var ReportApproveFlowCols = struct {
 	ReportType          string
 	ClassifyFirstId     string
 	ClassifySecondId    string
+	ClassifyThirdId     string
 	CurrVersion         string
 	CreateTime          string
 	ModifyTime          string
+	Enabled             string
 }{
 	ReportApproveFlowId: "report_approve_flow_id",
 	FlowName:            "flow_name",
 	ReportType:          "report_type",
 	ClassifyFirstId:     "classify_first_id",
 	ClassifySecondId:    "classify_second_id",
+	ClassifyThirdId:     "classify_third_id",
 	CurrVersion:         "curr_version",
 	CreateTime:          "create_time",
 	ModifyTime:          "modify_time",
+	Enabled:             "enabled",
 }
 
 func (m *ReportApproveFlow) TableName() string {
@@ -153,6 +159,7 @@ type ReportApproveFlowItem struct {
 	ReportType          int    `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId     int    `description:"一级分类ID"`
 	ClassifySecondId    int    `description:"二级分类ID"`
+	ClassifyThirdId     int    `description:"三级分类ID"`
 	ReportClassify      string `description:"报告类型: XX研报/一级分类/二级分类"`
 	CreateTime          string `description:"创建时间"`
 	ModifyTime          string `description:"修改时间"`
@@ -169,6 +176,7 @@ func FormatReportApproveFlow2Item(origin *ReportApproveFlow) (item *ReportApprov
 	item.ReportType = origin.ReportType
 	item.ClassifyFirstId = origin.ClassifyFirstId
 	item.ClassifySecondId = origin.ClassifySecondId
+	item.ClassifyThirdId = origin.ClassifyThirdId
 	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
 	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
 	return
@@ -180,6 +188,7 @@ type ReportApproveFlowAddReq struct {
 	ReportType       int                        `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId  int                        `description:"一级分类ID"`
 	ClassifySecondId int                        `description:"二级分类ID"`
+	ClassifyThirdId  int                        `description:"三级分类ID"`
 	Nodes            []ReportApproveNodeSaveReq `description:"审批节点信息"`
 }
 
@@ -304,7 +313,7 @@ func (m *ReportApproveFlow) UpdateFlowAndNodes(flowItem *ReportApproveFlow, node
 	}()
 
 	// 更新审批流
-	updateCols := []string{"FlowName", "ReportType", "ClassifyFirstId", "ClassifySecondId", "CurrVersion", "ModifyTime"}
+	updateCols := []string{"FlowName", "ReportType", "ClassifyFirstId", "ClassifySecondId", "ClassifyThirdId", "CurrVersion", "ModifyTime"}
 	if e = flowItem.Update(updateCols); e != nil {
 		err = fmt.Errorf("update flow err: %s", e.Error())
 		return
@@ -355,6 +364,7 @@ func FormatFlowAndNodesItem2Detail(flowItem *ReportApproveFlow, nodeItems []*Rep
 	detail.ReportType = flowItem.ReportType
 	detail.ClassifyFirstId = flowItem.ClassifyFirstId
 	detail.ClassifySecondId = flowItem.ClassifySecondId
+	detail.ClassifyThirdId = flowItem.ClassifyThirdId
 	detail.CreateTime = utils.TimeTransferString(utils.FormatDateTime, flowItem.CreateTime)
 	detail.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, flowItem.ModifyTime)
 	detail.Nodes = make([]*ReportApproveNodeItem, 0)
@@ -376,6 +386,7 @@ type ReportApproveFlowListReq struct {
 	ReportType       int    `form:"ReportType" description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId  int    `form:"ClassifyFirstId" description:"一级分类ID"`
 	ClassifySecondId int    `form:"ClassifySecondId" description:"二级分类ID"`
+	ClassifyThirdId  int    `form:"ClassifyThirdId" description:"三级级分类ID"`
 	Keyword          string `form:"Keyword" description:"关键词"`
 	SortRule         int    `form:"SortRule" description:"排序方式: 1-正序; 2-倒序(默认)"`
 }

+ 39 - 0
models/report_approve/report_approve_record.go

@@ -25,6 +25,10 @@ type ReportApproveRecord struct {
 	ApproveTime           time.Time `description:"审批时间"`
 	CreateTime            time.Time `description:"创建时间"`
 	ModifyTime            time.Time `description:"修改时间"`
+	NodeState             int       `description:"当前节点审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	NodeApproveUserId     int       `description:"当前节点审批人ID"`
+	NodeApproveUserName   string    `description:"当前节点审批人姓名"`
+	NodeApproveTime       time.Time `description:"当前节点审批时间"`
 }
 
 var ReportApproveRecordCols = struct {
@@ -43,6 +47,10 @@ var ReportApproveRecordCols = struct {
 	ApproveTime           string
 	CreateTime            string
 	ModifyTime            string
+	NodeState             string `description:"当前节点审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	NodeApproveUserId     string `description:"当前节点审批人ID"`
+	NodeApproveUserName   string `description:"当前节点审批人姓名"`
+	NodeApproveTime       string `description:"当前节点审批时间"`
 }{
 	ReportApproveRecordId: "report_approve_record_id",
 	ReportApproveId:       "report_approve_id",
@@ -59,6 +67,10 @@ var ReportApproveRecordCols = struct {
 	ApproveTime:           "approve_time",
 	CreateTime:            "create_time",
 	ModifyTime:            "modify_time",
+	NodeState:             "node_state",
+	NodeApproveUserId:     "node_approve_user_id",
+	NodeApproveUserName:   "node_approve_user_name",
+	NodeApproveTime:       "node_approve_time",
 }
 
 func (m *ReportApproveRecord) TableName() string {
@@ -209,6 +221,33 @@ func FormatReportApproveRecord2Item(origin *ReportApproveRecord) (item *ReportAp
 	return
 }
 
+// UpdateNodeState
+// @Description: 将该审批的同一个节点的记录标记为已审批
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-11 13:57:20
+// @param reportApproveId int
+// @param nodeId int
+// @param nodeState int
+// @param nodeApproveUserId int
+// @param nodeApproveUserName string
+// @param nodeApproveTime time.Time
+// @return err error
+func (m *ReportApproveRecord) UpdateNodeState(reportApproveId, nodeId, nodeState, nodeApproveUserId int, nodeApproveUserName string, nodeApproveTime time.Time) (err error) {
+	pars := make([]interface{}, 0)
+	pars = append(pars, nodeState, nodeApproveUserId, nodeApproveUserName, nodeApproveTime)
+
+	// 更新条件
+	whereParas := []interface{}{reportApproveId, nodeId}
+	pars = append(pars, whereParas)
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`UPDATE %s SET node_state=?,node_approve_user_id=?,node_approve_user_name=?,node_approve_time=? WHERE report_approve_id = ? AND node_id = ?`, m.TableName())
+	_, err = o.Raw(sql, pars).Exec()
+
+	return
+}
+
 // ReportApproveDetailNodeUserRecord 审批详情-节点用户审批记录
 type ReportApproveDetailNodeUserRecord struct {
 	ReportApproveRecordId int    `description:"审批记录ID"`

+ 351 - 43
models/report_chapter.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_mobile/models/report"
 	"eta/eta_mobile/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
@@ -8,33 +9,91 @@ import (
 
 // ReportChapter 报告章节
 type ReportChapter struct {
-	ReportChapterId   int       `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
-	ReportId          int       `description:"报告ID"`
-	ReportType        string    `description:"报告类型 day-晨报 week-周报"`
-	ClassifyIdFirst   int       `description:"一级分类id"`
-	ClassifyNameFirst string    `description:"一级分类名称"`
-	TypeId            int       `description:"品种ID"`
-	TypeName          string    `description:"品种名称"`
-	Title             string    `description:"标题"`
-	Abstract          string    `description:"摘要"`
-	AddType           int       `description:"新增方式:1:新增报告,2:继承报告"`
-	Author            string    `description:"作者"`
-	Content           string    `description:"内容"`
-	ContentSub        string    `description:"内容前两个章节"`
-	Stage             int       `description:"期数"`
-	Trend             string    `description:"趋势观点"`
-	Sort              int       `description:"排序: 数值越小越靠前"`
-	IsEdit            int       `description:"是否已编辑 0-待编辑 1-已编辑"`
-	PublishState      int       `description:"发布状态 1-待发布,2-已发布"`
-	PublishTime       time.Time `description:"发布时间"`
-	VideoUrl          string    `description:"音频文件URL"`
-	VideoName         string    `description:"音频文件名称"`
-	VideoPlaySeconds  string    `description:"音频播放时长"`
-	VideoSize         string    `description:"音频文件大小,单位M"`
-	VideoKind         int       `description:"音频生成方式:1,手动上传,2:自动生成"`
-	CreateTime        string    `description:"创建时间"`
-	ModifyTime        time.Time `description:"修改时间"`
-	OriginalVideoUrl  string    `description:"原始音频文件URL"`
+	ReportChapterId     int       `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
+	ReportId            int       `description:"报告ID"`
+	ReportType          string    `description:"报告类型 day-晨报 week-周报"`
+	ClassifyIdFirst     int       `description:"一级分类id"`
+	ClassifyNameFirst   string    `description:"一级分类名称"`
+	TypeId              int       `description:"品种ID"`
+	TypeName            string    `description:"品种名称"`
+	Title               string    `description:"标题"`
+	Abstract            string    `description:"摘要"`
+	AddType             int       `description:"新增方式:1:新增报告,2:继承报告"`
+	Author              string    `description:"作者"`
+	Content             string    `description:"内容"`
+	ContentSub          string    `description:"内容前两个章节"`
+	Stage               int       `description:"期数"`
+	Trend               string    `description:"趋势观点"`
+	Sort                int       `description:"排序: 数值越小越靠前"`
+	IsEdit              int       `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState        int       `description:"发布状态 1-待发布,2-已发布"`
+	PublishTime         time.Time `description:"发布时间"`
+	VideoUrl            string    `description:"音频文件URL"`
+	VideoName           string    `description:"音频文件名称"`
+	VideoPlaySeconds    string    `description:"音频播放时长"`
+	VideoSize           string    `description:"音频文件大小,单位M"`
+	VideoKind           int       `description:"音频生成方式:1,手动上传,2:自动生成"`
+	CreateTime          string    `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+	OriginalVideoUrl    string    `description:"原始音频文件URL"`
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	VoiceGenerateType   int       `description:"音频生成方式,0:系统生成,1:人工上传"`
+}
+
+// ReportChapterItem 报告章节详情
+type ReportChapterItem struct {
+	ReportChapterId     int    `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
+	ReportId            int    `description:"报告ID"`
+	ReportType          string `description:"报告类型 day-晨报 week-周报"`
+	ClassifyIdFirst     int    `description:"一级分类id"`
+	ClassifyNameFirst   string `description:"一级分类名称"`
+	TypeId              int    `description:"品种ID"`
+	TypeName            string `description:"品种名称"`
+	Title               string `description:"标题"`
+	Abstract            string `description:"摘要"`
+	AddType             int    `description:"新增方式:1:新增报告,2:继承报告"`
+	Author              string `description:"作者"`
+	Content             string `description:"内容"`
+	ContentSub          string `description:"内容前两个章节"`
+	Stage               int    `description:"期数"`
+	Trend               string `description:"趋势观点"`
+	Sort                int    `description:"排序: 数值越小越靠前"`
+	IsEdit              int    `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState        int    `description:"发布状态 1-待发布,2-已发布"`
+	PublishTime         string `description:"发布时间"`
+	VideoUrl            string `description:"音频文件URL"`
+	VideoName           string `description:"音频文件名称"`
+	VideoPlaySeconds    string `description:"音频播放时长"`
+	VideoSize           string `description:"音频文件大小,单位M"`
+	VideoKind           int    `description:"音频生成方式:1,手动上传,2:自动生成"`
+	CreateTime          string `description:"创建时间"`
+	ModifyTime          string `description:"修改时间"`
+	OriginalVideoUrl    string `description:"原始音频文件URL"`
+	ContentStruct       string `description:"内容组件"`
+	LastModifyAdminId   int    `description:"最后更新人ID"`
+	LastModifyAdminName string `description:"最后更新人姓名"`
+	ContentModifyTime   string `description:"内容更新时间"`
+	ReportLayout        int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	ReportCreateTime    string `description:"报告时间创建时间"`
+}
+
+// ReportChapterItemResp
+// @Description: 章节详情(带有一些额外的数据)
+type ReportChapterItemResp struct {
+	ReportChapterItem
+	GrandAdminIdList []int  `description:"授权的用户id列表"`
+	PermissionIdList []int  `description:"关联的品种id列表"`
+	CanEdit          bool   `description:"是否可编辑"`
+	Editor           string `description:"编辑人"`
+	HeadImg          string `description:"报告头图地址"`
+	EndImg           string `description:"报告尾图地址"`
+	HeadStyle        string `description:"版头样式"`
+	EndStyle         string `description:"版尾样式"`
 }
 
 type ReportChapterResp struct {
@@ -62,6 +121,11 @@ type ReportChapterResp struct {
 	PublishTime      string `description:"发布时间"`
 	CreateTime       string `description:"创建时间"`
 	ModifyTime       string `description:"修改时间"`
+	GrandAdminIdList []int  `description:"授权的用户id列表"`
+	PermissionIdList []int  `description:"关联的品种id列表"`
+	CanEdit          bool   `description:"是否可编辑"`
+	Editor           string `description:"编辑人"`
+	IsAuth           bool   `description:"是否有权限"`
 }
 
 // GetChapterListByReportId 根据ReportId获取章节列表
@@ -82,6 +146,15 @@ func GetPublishedChapterListByReportId(reportId int) (list []*ReportChapter, err
 	return
 }
 
+// AddReportChapterReq
+// @Description: 新增报告章节请求体
+type AddReportChapterReq struct {
+	ReportId         int    `description:"报告ID"`
+	Title            string `description:"标题"`
+	PermissionIdList []int  `description:"报告关联的品种权限"`
+	AdminIdList      []int  `description:"授权的编辑人id列表"`
+}
+
 // EditReportChapterReq 编辑报告章节请求体
 type EditReportChapterReq struct {
 	ReportChapterId  int            `description:"报告章节ID"`
@@ -95,6 +168,14 @@ type EditReportChapterReq struct {
 	VideoName        string         `description:"音频文件名称"`
 	VideoPlaySeconds string         `description:"音频播放时长"`
 	VideoSize        string         `description:"音频文件大小,单位M"`
+
+	// 以下是智能研报相关
+	ContentStruct  string `description:"内容组件"`
+	HeadImg        string `description:"报告头图地址"`
+	EndImg         string `description:"报告尾图地址"`
+	CanvasColor    string `description:"画布颜色"`
+	HeadResourceId int    `description:"版头资源ID"`
+	EndResourceId  int    `description:"版尾资源ID"`
 }
 
 type EditTickList struct {
@@ -112,6 +193,21 @@ func GetReportChapterInfoById(reportChapterId int) (item *ReportChapter, err err
 	return
 }
 
+// GetReportChapterItemById
+// @Description: 根据主键获取报告章节(时间格式为字符串的数据)
+// @author: Roc
+// @datetime 2024-06-27 14:10:29
+// @param reportChapterId int
+// @return item *ReportChapterItem
+// @return err error
+func GetReportChapterItemById(reportChapterId int) (item *ReportChapterItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_chapter_id = ? `
+	err = o.Raw(sql, reportChapterId).QueryRow(&item)
+
+	return
+}
+
 // GetLastPublishedReportChapter 获取上一篇已发表的晨周报章节
 func GetLastPublishedReportChapter(typeId int, reportType string) (item *ReportChapter, err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -121,10 +217,27 @@ func GetLastPublishedReportChapter(typeId int, reportType string) (item *ReportC
 	return
 }
 
+// Add
+// @Description: 新增章节报告
+// @author: Roc
+// @receiver chapterInfo
+// @datetime 2024-06-04 15:14:41
+// @return err error
+func (chapterChapterInfo *ReportChapter) Add() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err := o.Insert(chapterChapterInfo)
+	if err != nil {
+		return
+	}
+	chapterChapterInfo.ReportChapterId = int(lastId)
+
+	return
+}
+
 // UpdateChapter 更新报表章节
-func (chapterInfo *ReportChapter) UpdateChapter(cols []string) (err error) {
+func (chapterChapterInfo *ReportChapter) UpdateChapter(cols []string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	_, err = o.Update(chapterInfo, cols...)
+	_, err = o.Update(chapterChapterInfo, cols...)
 
 	return
 }
@@ -136,7 +249,11 @@ type EditChapterTrendTagReq struct {
 }
 
 // UpdateChapterAndTicker 更新章节及ticker
-func UpdateChapterAndTicker(chapterInfo *ReportChapter, updateCols []string, tickerList []*ReportChapterTicker) (err error) {
+func UpdateChapterAndTicker(reportInfo *Report, chapterInfo *ReportChapter, updateCols []string, tickerList []*ReportChapterTicker) (err error) {
+	// 更新报告的最近编辑人信息
+	if err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}); err != nil {
+		return
+	}
 	// 更新章节
 	if err = chapterInfo.UpdateChapter(updateCols); err != nil {
 		return
@@ -188,7 +305,7 @@ func GetReportChapterVideoList(reportId int) (list []*ReportChapterVideoList, er
 }
 
 // GetReportChapterVideoListByReportIds 根据报告ID集合获取报告章节音频列表
-func GetReportChapterVideoListByReportIds(reportIds []string) (list []*ReportChapterVideoList, err error) {
+func GetReportChapterVideoListByReportIds(reportIds []int) (list []*ReportChapterVideoList, err error) {
 	if len(reportIds) == 0 {
 		return
 	}
@@ -234,18 +351,8 @@ func GetReportChapterVideoListByChapterIds(chapterIds []int) (list []*ReportChap
 
 // PublishReportChapterReq 发布报告章节请求体
 type PublishReportChapterReq struct {
-	ReportChapterId  int            `description:"报告章节ID"`
-	Title            string         `description:"标题"`
-	AddType          int            `description:"新增方式:1:新增报告,2:继承报告"`
-	Author           string         `description:"作者"`
-	Content          string         `description:"内容"`
-	TickerList       []EditTickList `description:"指标信息"`
-	CreateTime       string         `description:"发布时间"`
-	PublishReport    int            `description:"是否同时发布报告"`
-	VideoUrl         string         `description:"音频文件URL"`
-	VideoName        string         `description:"音频文件名称"`
-	VideoPlaySeconds string         `description:"音频播放时长"`
-	VideoSize        string         `description:"音频文件大小,单位M"`
+	ReportChapterId int `description:"报告章节ID"`
+	PublishReport   int `description:"是否同时发布报告"`
 }
 
 // CountPublishedChapterNum 获取报告已发布的章节数
@@ -257,6 +364,36 @@ func CountPublishedChapterNum(reportId int) (count int, err error) {
 	return
 }
 
+// CountUnPublishedChapterNum
+// @Description: 获取报告未发布的章节数
+// @author: Roc
+// @datetime 2024-06-14 15:59:23
+// @param reportId int
+// @return count int
+// @return err error
+func CountUnPublishedChapterNum(reportId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS ct FROM report_chapter WHERE report_id = ? AND publish_state = 1 `
+	err = o.Raw(sql, reportId).QueryRow(&count)
+
+	return
+}
+
+// GetUnPublishedChapterList
+// @Description: 获取报告未发布的章节列表
+// @author: Roc
+// @datetime 2024-06-14 15:59:23
+// @param reportId int
+// @return list []*ReportChapter
+// @return err error
+func GetUnPublishedChapterList(reportId int) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT report_chapter_id,report_id,title FROM report_chapter WHERE report_id = ? AND publish_state = 1 ORDER BY sort ASC`
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
 // GetChapterListByChapterIds 根据ReportId获取章节列表
 func GetChapterListByChapterIds(chapterIds []int) (list []*ReportChapter, err error) {
 	if len(chapterIds) == 0 {
@@ -305,3 +442,174 @@ func CountReportChapterByTypeId(typeId int) (count int, err error) {
 	err = o.Raw(sql, typeId).QueryRow(&count)
 	return
 }
+
+// AddReportChapter
+// @Description: 待添加的报告章节
+type AddReportChapter struct {
+	ReportChapter       *ReportChapter
+	GrantList           []*report.ReportChapterGrant
+	GrantPermissionList []*report.ReportChapterPermissionMapping
+}
+
+// EditReportChapterBaseInfoAndPermissionReq
+// @Description: 编辑报告章节的基础信息请求
+type EditReportChapterBaseInfoAndPermissionReq struct {
+	ReportChapterId  int    `description:"报告章节ID"`
+	Title            string `description:"标题"`
+	PermissionIdList []int  `description:"报告关联的品种权限"`
+	AdminIdList      []int  `description:"授权的编辑人id列表"`
+}
+
+// GetReportChapterIdList
+// @Description: 获取报告的所有章节id列表
+// @author: Roc
+// @datetime 2024-06-05 11:09:40
+// @param reportId int
+// @return list []int
+// @return err error
+func GetReportChapterIdList(reportId int) (list []int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT report_chapter_id FROM report_chapter
+			WHERE report_id = ? 
+			ORDER BY report_chapter_id ASC `
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// ReportChapterMoveReq
+// @Description:  报告章节移动请求
+type ReportChapterMoveReq struct {
+	ReportChapterId int `description:"报告章节id"`
+	//	ParentChartPermissionId int `description:"父级品种id"`
+	PrevReportChapterId int `description:"上一个兄弟节点报告章节id"`
+	NextReportChapterId int `description:"下一个兄弟节点报告章节id"`
+}
+
+// GetReportChapterById
+// @Description: 获取具体章节
+// @author: Roc
+// @receiver r
+// @datetime 2024-06-06 09:32:40
+// @param reportChapterId int
+// @return item *ReportChapter
+// @return err error
+func (chapterChapterInfo *ReportChapter) GetReportChapterById(reportChapterId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_chapter_id = ?`
+	err = o.Raw(sql, reportChapterId).QueryRow(&item)
+	return
+}
+
+// UpdateReportChapterSortByReportId
+// @Description: 根据父类id更新排序
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:39:28
+// @param reportId int
+// @param reportChapterId int
+// @param nowSort int
+// @param updateSort string
+// @return err error
+func (chapterChapterInfo *ReportChapter) UpdateReportChapterSortByReportId(reportId, reportChapterId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	pars := make([]interface{}, 0)
+	sql := ` update report_chapter set sort = ` + updateSort + ` WHERE report_id = ? AND sort > ?`
+	pars = append(pars, reportId, nowSort)
+
+	if reportChapterId > 0 {
+		sql += ` or ( report_chapter_id > ?  and sort = ? )`
+		pars = append(pars, reportChapterId, nowSort)
+	}
+
+	_, err = o.Raw(sql, pars).Exec()
+
+	return
+}
+
+// GetFirstReportChapterByReportId
+// @Description: 获取当前报告下,且排序数相同 的排序第一条的数据
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:45:32
+// @param reportId int
+// @return item *ReportChapter
+// @return err error
+func (chapterChapterInfo *ReportChapter) GetFirstReportChapterByReportId(reportId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE 1 = 1 AND report_id = ? ORDER BY sort ASC, report_chapter_id ASC LIMIT 1`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// GetMaxSortByReportId
+// @Description: 获取最大的排序值
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:44:13
+// @param reportId int
+// @return maxSort int
+// @return err error
+func (chapterChapterInfo *ReportChapter) GetMaxSortByReportId(reportId int) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM report_chapter WHERE report_id = ?`
+	err = o.Raw(sql, reportId).QueryRow(&maxSort)
+
+	return
+}
+
+// Update
+// @Description: 数据变更
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:47:46
+// @param cols []string
+// @return err error
+func (chapterChapterInfo *ReportChapter) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(chapterChapterInfo, cols...)
+
+	return
+}
+
+// DelReportChapterReq
+// @Description: 删除报告章节请求体
+type DelReportChapterReq struct {
+	ReportChapterId int `description:"报告章节ID"`
+}
+
+// GetAllReportChapter 获取所有的报告章节
+func GetAllReportChapter() (items []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter ORDER BY report_chapter_id asc `
+	_, err = o.Raw(sql).QueryRows(&items)
+
+	return
+}
+
+// GetCountReportChapterByCondition
+// @Description: 根据条件获取章节数量
+// @author: Roc
+// @datetime 2024-07-15 15:37:50
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetCountReportChapterByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS count FROM report_chapter WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+
+	return
+}
+
+// GetNewestPreReportChapterByClassifyIdAndTypeId 获取分类下往期中最新发布的系统章节
+func GetNewestPreReportChapterByClassifyIdAndTypeId(classifyId, typeId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.* FROM report_chapter AS a JOIN report AS b ON a.report_id = b.id WHERE a.classify_id_first = ? AND a.type_id = ? AND a.publish_state = 2 AND b.state IN (2,6) ORDER BY a.publish_time DESC LIMIT 1`
+	err = o.Raw(sql, classifyId, typeId).QueryRow(&item)
+	return
+}

+ 2 - 2
models/report_chapter_ticker.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_mobile/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -61,8 +62,7 @@ func GetDailyBaseColumnList(keyword string, typeId int) (list []*DailyBaseColumn
 	sql := ` SELECT * FROM daily_base_column WHERE 1 = 1 `
 	pars := make([]interface{}, 0)
 	if keyword != "" {
-		keyword = "%" + keyword + "%"
-		pars = append(pars, keyword)
+		pars = append(pars, utils.GetLikeKeyword(keyword))
 		sql += ` AND base_column_name like ? `
 	}
 	pars = append(pars, typeId)

+ 71 - 24
models/report_chapter_type.go

@@ -8,28 +8,30 @@ import (
 )
 
 type ReportChapterType struct {
-	ReportChapterTypeId    int       `orm:"column(report_chapter_type_id);pk" description:"报告章节类型id"`
-	ReportChapterTypeKey   string    `description:"章节key"`
-	ReportChapterTypeThumb string    `description:"H5展示的图片"`
-	BannerUrl              string    `description:"banner显示图片"`
-	ReportChapterTypeName  string    `description:"报告章节类型名称"`
-	Sort                   int       `description:"排序字段"`
-	Enabled                int       `description:"启禁用状态"`
-	CreatedTime            time.Time `description:"创建时间"`
-	LastUpdatedTime        time.Time `description:"更新时间"`
-	ResearchType           string    `description:"研报类型"`
-	SelectedImage          string    `description:"选中时的图片"`
-	UnselectedImage        string    `description:"没选中时的图片"`
-	PcSelectedImage        string    `description:"PC-选中的图片"`
-	PcUnselectedImage      string    `description:"PC-未选中的图片"`
-	EditImgUrl             string    `description:"管理后台编辑时选用的图"`
-	TickerTitle            string    `description:"指标列的标题"`
-	IsShow                 int       `description:"是否显示(研报小程序端根据此字段判断)"`
-	PauseStartTime         string    `description:"暂停开始日期"`
-	PauseEndTime           string    `description:"暂停结束日期"`
-	IsSet                  int       `description:"是否设置:0为设置,1已设置"`
-	YbIconUrl              string    `description:"研报小程序icon"`
-	YbBottomIcon           string    `description:"研报小程序详情底部icon"`
+	ReportChapterTypeId        int       `orm:"column(report_chapter_type_id);pk" description:"报告章节类型id"`
+	ReportChapterTypeKey       string    `description:"章节key"`
+	ReportChapterTypeThumb     string    `description:"H5展示的图片"`
+	BannerUrl                  string    `description:"banner显示图片"`
+	ReportChapterTypeName      string    `description:"报告章节类型名称"`
+	Sort                       int       `description:"排序字段"`
+	Enabled                    int       `description:"启禁用状态"`
+	CreatedTime                time.Time `description:"创建时间"`
+	LastUpdatedTime            time.Time `description:"更新时间"`
+	ResearchType               string    `description:"研报类型"`
+	SelectedImage              string    `description:"选中时的图片"`
+	UnselectedImage            string    `description:"没选中时的图片"`
+	PcSelectedImage            string    `description:"PC-选中的图片"`
+	PcUnselectedImage          string    `description:"PC-未选中的图片"`
+	EditImgUrl                 string    `description:"管理后台编辑时选用的图"`
+	TickerTitle                string    `description:"指标列的标题"`
+	IsShow                     int       `description:"是否显示(研报小程序端根据此字段判断)"`
+	PauseStartTime             string    `description:"暂停开始日期"`
+	PauseEndTime               string    `description:"暂停结束日期"`
+	IsSet                      int       `description:"是否设置:0为设置,1已设置"`
+	YbIconUrl                  string    `description:"研报小程序icon"`
+	YbBottomIcon               string    `description:"研报小程序详情底部icon"`
+	ReportClassifyId           int       `description:"所属分类id"`
+	InheritReportChapterTypeId int       `description:"继承的报告章节类型id"`
 }
 
 func (r *ReportChapterType) Create() (err error) {
@@ -42,6 +44,23 @@ func (r *ReportChapterType) Create() (err error) {
 	return
 }
 
+// MultiCreate
+// @Description: 批量添加报告章节类型
+// @author: Roc
+// @receiver r
+// @datetime 2024-06-17 14:36:09
+// @param addList []*ReportChapterType
+// @return err error
+func (r *ReportChapterType) MultiCreate(addList []*ReportChapterType) (err error) {
+	if len(addList) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(500, addList)
+
+	return
+}
+
 func (r *ReportChapterType) Update(cols []string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	_, err = o.Update(r, cols...)
@@ -341,7 +360,7 @@ type ReportChapterTypeListItem struct {
 // ReportChapterTypeAddReq 新增章节类型请求体
 type ReportChapterTypeAddReq struct {
 	ReportChapterTypeName string `description:"报告章节类型名称"`
-	ResearchType          string `description:"研报类型"`
+	ClassifyId            int    `description:"所属报告分类id"`
 	ChartPermissionIdList []int  `description:"权限id数组"`
 	/*SelectedImage         string `description:"选中时的icon"`
 	UnselectedImage       string `description:"未选中时的icon"`
@@ -354,7 +373,7 @@ type ReportChapterTypeAddReq struct {
 type ReportChapterTypeEditReq struct {
 	ReportChapterTypeId   int    `description:"报告章节类型id"`
 	ReportChapterTypeName string `description:"报告章节类型名称"`
-	ResearchType          string `description:"研报类型"`
+	ClassifyId            int    `description:"所属报告分类id"`
 	ChartPermissionIdList []int  `description:"权限id数组"`
 	/*SelectedImage         string `description:"选中时的icon"`
 	UnselectedImage       string `description:"未选中时的icon"`
@@ -447,3 +466,31 @@ func (r *ReportChapterType) SetEnabled(id, enabled int) (err error) {
 	_, err = o.Raw(sql, enabled, id).Exec()
 	return
 }
+
+// GetReportChapterTypeListByClassifyId
+// @Description: 通过报告类型获取章节类型列表(已启用的)
+// @author: Roc
+// @datetime 2024-06-03 16:00:12
+// @param classifyId int
+// @return list []*ReportChapterType
+// @return err error
+func GetReportChapterTypeListByClassifyId(classifyId int) (list []*ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type WHERE report_classify_id = ? AND enabled = 1`
+	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	return
+}
+
+// GetAllReportChapterTypeListByClassifyId
+// @Description: 通过报告类型获取所有的章节类型列表
+// @author: Roc
+// @datetime 2024-06-03 16:00:12
+// @param classifyId int
+// @return list []*ReportChapterType
+// @return err error
+func GetAllReportChapterTypeListByClassifyId(classifyId int) (list []*ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type WHERE report_classify_id = ? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	return
+}

+ 60 - 22
models/report_chapter_type_permission.go

@@ -16,16 +16,22 @@ type ReportChapterTypePermission struct {
 	CreatedTime           time.Time `description:"创建时间"`
 }
 
-// GetChapterTypePermissionByTypeIdAndResearchType 根据章节类型ID及研报类型获取章节类型权限列表
-func GetChapterTypePermissionByTypeIdAndResearchType(typeId int, researchType string) (list []*ReportChapterTypePermission, err error) {
+// GetChapterTypePermissionByReportChapterTypeId
+// @Description: 根据章节类型ID获取章节类型权限列表
+// @author: Roc
+// @datetime 2024-06-03 15:42:47
+// @param typeId int
+// @return list []*ReportChapterTypePermission
+// @return err error
+func GetChapterTypePermissionByReportChapterTypeId(typeId int) (list []*ReportChapterTypePermission, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ? ORDER BY chart_permission_id ASC `
-	_, err = o.Raw(sql, typeId, researchType).QueryRows(&list)
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ?  ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, typeId).QueryRows(&list)
 	return
 }
 
 // SetReportChapterTypePermission 设置报告章节类型权限
-func SetReportChapterTypePermission(chapterTypeId int, researchType string, newPermissions []*ReportChapterTypePermission, newWeekPermissions []*ChartPermissionChapterMapping) (err error) {
+func SetReportChapterTypePermission(chapterTypeId int, newPermissions []*ReportChapterTypePermission) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
@@ -40,8 +46,8 @@ func SetReportChapterTypePermission(chapterTypeId int, researchType string, newP
 	}()
 
 	// 删除原章节类型权限
-	sql := `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ?`
-	_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
+	sql := `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ?`
+	_, err = to.Raw(sql, chapterTypeId).Exec()
 	if err != nil {
 		return
 	}
@@ -55,22 +61,23 @@ func SetReportChapterTypePermission(chapterTypeId int, researchType string, newP
 	}
 
 	// 周报章节调整chart_permission_chapter_mapping表
-	if researchType == utils.REPORT_TYPE_WEEK {
-		// 删除原权限
-		sql = `DELETE FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ?`
-		_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
-		if err != nil {
-			return
-		}
+	//if researchType == utils.REPORT_TYPE_WEEK {
+	//	// 删除原权限
+	//	sql = `DELETE FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ?`
+	//	_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
+	//	if err != nil {
+	//		return
+	//	}
+	//
+	//	// 新增权限
+	//	if len(newWeekPermissions) > 0 {
+	//		_, err = to.InsertMulti(len(newWeekPermissions), newWeekPermissions)
+	//		if err != nil {
+	//			return
+	//		}
+	//	}
+	//}
 
-		// 新增权限
-		if len(newWeekPermissions) > 0 {
-			_, err = to.InsertMulti(len(newWeekPermissions), newWeekPermissions)
-			if err != nil {
-				return
-			}
-		}
-	}
 	return
 }
 
@@ -81,3 +88,34 @@ func GetChapterTypePermissionByResearchType(researchType string) (list []*Report
 	_, err = o.Raw(sql, researchType).QueryRows(&list)
 	return
 }
+
+// GetChapterTypePermissionByChapterTypeIdList
+// @Description: 根据章节类型ID列表获取章节类型权限列表
+// @author: Roc
+// @datetime 2024-06-03 14:10:17
+// @param chapterTypeIdList []int
+// @return list []*ReportChapterTypePermission
+// @return err error
+func GetChapterTypePermissionByChapterTypeIdList(chapterTypeIdList []int) (list []*ReportChapterTypePermission, err error) {
+	num := len(chapterTypeIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id in (` + utils.GetOrmInReplace(num) + `) ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, chapterTypeIdList).QueryRows(&list)
+	return
+}
+
+// GetAllChapterTypePermission
+// @Description: 获取所有章节类型ID获取章节类型权限列表
+// @author: Roc
+// @datetime 2024-06-03 15:42:47
+// @return list []*ReportChapterTypePermission
+// @return err error
+func GetAllChapterTypePermission() (list []*ReportChapterTypePermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type_permission ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}

+ 453 - 0
models/report_v2.go

@@ -0,0 +1,453 @@
+package models
+
+import (
+	"errors"
+	"eta/eta_mobile/models/report"
+	"eta/eta_mobile/utils"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// AddReportAndChapter
+// @Description: 新增报告及章节
+// @author: Roc
+// @datetime 2024-06-06 17:08:34
+// @param reportItem *Report
+// @param allGrantUserList []*report.ReportGrant
+// @param addReportChapterList []AddReportChapter
+// @return reportId int64
+// @return err error
+func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGrant, addReportChapterList []AddReportChapter) (reportId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 新增报告
+	reportId, err = to.Insert(reportItem)
+	if err != nil {
+		return
+	}
+	reportItem.Id = int(reportId)
+
+	// 新增报告授权
+	if len(allGrantUserList) > 0 {
+		for _, v := range allGrantUserList {
+			v.ReportId = reportItem.Id
+		}
+		_, err = to.InsertMulti(500, allGrantUserList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节
+	if len(addReportChapterList) > 0 {
+		for _, addReportChapter := range addReportChapterList {
+			// 新增章节
+			chapterItem := addReportChapter.ReportChapter
+			chapterItem.ReportId = int(reportId)
+			cpId, tmpErr := to.Insert(chapterItem)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			chapterItem.ReportChapterId = int(cpId)
+
+			// 新增章节授权
+			if len(addReportChapter.GrantList) > 0 {
+				grantList := addReportChapter.GrantList
+				for _, v := range grantList {
+					v.ReportChapterId = chapterItem.ReportChapterId
+				}
+				_, err = to.InsertMulti(500, grantList)
+				if err != nil {
+					return
+				}
+			}
+
+			// 新增报告章节关联的品种
+			if len(addReportChapter.GrantPermissionList) > 0 {
+				permissionList := addReportChapter.GrantPermissionList
+				for _, v := range permissionList {
+					v.ReportChapterId = chapterItem.ReportChapterId
+				}
+				_, err = to.InsertMulti(500, permissionList)
+				if err != nil {
+					return
+				}
+			}
+
+		}
+	}
+
+	return
+}
+
+// EditReportAndPermission
+// @Description: 修改报告的基础信息、授权用户权限
+// @author: Roc
+// @datetime 2024-06-06 17:11:12
+// @param reportInfo *Report
+// @param updateCols []string
+// @param addReportGrantList []*report.ReportGrant
+// @param delReportGrantIdList []int
+// @return err error
+func EditReportAndPermission(reportInfo *Report, updateCols []string, addReportGrantList []*report.ReportGrant, delReportGrantIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 变更报告章节信息
+	if len(updateCols) > 0 {
+		_, err = to.Update(reportInfo, updateCols...)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告授权用户
+	if len(addReportGrantList) > 0 {
+		_, err = to.InsertMulti(500, addReportGrantList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告授权用户
+	delNum := len(delReportGrantIdList)
+	if delNum > 0 {
+		sql := `DELETE FROM report_grant WHERE grant_id IN (` + utils.GetOrmInReplace(delNum) + `)`
+		_, err = to.Raw(sql, delReportGrantIdList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// AddChapterBaseInfoAndPermission
+// @Description: 新增报告章节的基础信息、授权用户权限
+// @author: Roc
+// @datetime 2024-06-11 15:33:50
+// @param reportChapterInfo *ReportChapter
+// @param addReportChapterGrantList []*report.ReportChapterGrant
+// @param addChapterPermissionMap []*report.ReportChapterPermissionMapping
+// @return err error
+func AddChapterBaseInfoAndPermission(reportChapterInfo *ReportChapter, addReportChapterGrantList []*report.ReportChapterGrant, addChapterPermissionMap []*report.ReportChapterPermissionMapping) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	lastId, err := to.Insert(reportChapterInfo)
+	if err != nil {
+		return
+	}
+	reportChapterInfo.ReportChapterId = int(lastId)
+
+	// 新增报告章节授权用户
+	if len(addReportChapterGrantList) > 0 {
+		for k, _ := range addReportChapterGrantList {
+			addReportChapterGrantList[k].ReportChapterId = reportChapterInfo.ReportChapterId
+		}
+		_, err = to.InsertMulti(500, addReportChapterGrantList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节关联的品种配置
+	if len(addChapterPermissionMap) > 0 {
+		for k, _ := range addChapterPermissionMap {
+			addChapterPermissionMap[k].ReportChapterId = reportChapterInfo.ReportChapterId
+		}
+
+		_, err = to.InsertMulti(500, addChapterPermissionMap)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// EditChapterBaseInfoAndPermission
+// @Description: 修改报告章节的基础信息、授权用户权限、品种权限
+// @author: Roc
+// @datetime 2024-06-05 11:45:04
+// @param reportChapterInfo *ReportChapter
+// @param updateCols []string
+// @param addReportChapterGrantList []report.ReportChapterGrant
+// @param addChapterPermissionMap []*report.ReportChapterPermissionMapping
+// @param delReportChapterGrantIdList []int
+// @param delChapterPermissionMappingIdList []int
+// @return err error
+func EditChapterBaseInfoAndPermission(reportInfo *Report, reportChapterInfo *ReportChapter, updateCols []string, addReportChapterGrantList []*report.ReportChapterGrant, addChapterPermissionMap []*report.ReportChapterPermissionMapping, delReportChapterGrantIdList, delChapterPermissionMappingIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 变更报告的最后编辑人信息
+	{
+		_, err = to.Update(reportInfo, "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+		if err != nil {
+			return
+		}
+	}
+
+	// 变更报告章节信息
+	if len(updateCols) > 0 {
+		_, err = to.Update(reportChapterInfo, updateCols...)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节授权用户
+	if len(addReportChapterGrantList) > 0 {
+		_, err = to.InsertMulti(500, addReportChapterGrantList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节授权用户
+	delNum := len(delReportChapterGrantIdList)
+	if delNum > 0 {
+		sql := `DELETE FROM report_chapter_grant WHERE grant_id IN (` + utils.GetOrmInReplace(delNum) + `)`
+		_, err = to.Raw(sql, delReportChapterGrantIdList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节的品种配置
+	if len(addChapterPermissionMap) > 0 {
+		_, err = to.InsertMulti(500, addChapterPermissionMap)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节的品种配置
+	delNum = len(delChapterPermissionMappingIdList)
+	if delNum > 0 {
+		sql := `DELETE FROM report_chapter_permission_mapping WHERE report_chapter_permission_mapping_id IN (` + utils.GetOrmInReplace(delNum) + `)`
+		_, err = to.Raw(sql, delChapterPermissionMappingIdList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// DelChapterAndPermission
+// @Description: 删除报告章节、授权用户权限、品种权限
+// @author: Roc
+// @datetime 2024-06-06 17:25:47
+// @param reportInfo *Report
+// @param updateReportCols []string
+// @param reportChapterInfo *ReportChapter
+// @return err error
+func DelChapterAndPermission(reportInfo *Report, updateReportCols []string, reportChapterInfo *ReportChapter) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 变更报告信息
+	if len(updateReportCols) > 0 {
+		_, err = to.Update(reportInfo, updateReportCols...)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告对应章节
+	{
+		_, err = to.Delete(reportChapterInfo)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节的授权用户权限
+	{
+		sql := `DELETE FROM report_chapter_grant WHERE report_chapter_id = ? `
+		_, err = to.Raw(sql, reportChapterInfo.ReportChapterId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节的品种配置
+	{
+		sql := `DELETE FROM report_chapter_permission_mapping WHERE report_chapter_id = ? `
+		_, err = to.Raw(sql, reportChapterInfo.ReportChapterId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// GetReportListCountByAuthorized
+// @Description: 获取有权限的报告列表的报告数量
+// @author: Roc
+// @datetime 2024-05-30 15:14:01
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetReportListCountByAuthorized(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM report as a 
+ WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetReportListByAuthorized
+// @Description: 获取有权限的报告列表的数据
+// @author: Roc
+// @datetime 2024-05-30 15:15:07
+// @param condition string
+// @param pars []interface{}
+// @param startSize int
+// @param pageSize int
+// @return items []*ReportList
+// @return err error
+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  `
+	if condition != "" {
+		sql += condition
+	}
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += ` GROUP BY a.id ORDER BY  report_create_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ModifyReportClassifyAndReportChapterTypeByCondition
+// @Description:
+// @author: Roc
+// @datetime 2024-06-17 16:12:44
+// @param condition string
+// @param pars []interface{}
+// @param updateStr string
+// @param chapterTypeIdMap map[int]int 当前的章节类型ID ---> 继承的章节类型ID
+// @param oldClassifyId int
+// @param currClassifyId int
+// @param currClassifyName string
+// @return err error
+func ModifyReportClassifyAndReportChapterTypeByCondition(condition string, pars []interface{}, updateStr string, chapterTypeIdMap map[int]int, oldClassifyId, currClassifyId int, currClassifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	if condition == `` {
+		err = errors.New("condition不能为空")
+		return
+	}
+	// 修改报告的所属分类
+	sql := `UPDATE report as a SET ` + updateStr + ` WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = to.Raw(sql, pars).Exec()
+	if err != nil {
+		return
+	}
+
+	// 修改历史报告中的章节分类归属
+	sql = `UPDATE report_chapter set classify_id_first=?,classify_name_first=? where classify_id_first = ?`
+	_, err = to.Raw(sql, currClassifyId, currClassifyName, oldClassifyId).Exec()
+	if err != nil {
+		return
+	}
+
+	for currTypeId, oldTypeId := range chapterTypeIdMap {
+		// 没有章节类型的不处理
+		if oldTypeId == 0 {
+			continue
+		}
+		tmpSql := `UPDATE report_chapter set type_id=? where type_id = ?`
+		_, err = to.Raw(tmpSql, currTypeId, oldTypeId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// EditLayoutImgReq
+// @Description: 版图设置请求
+type EditLayoutImgReq struct {
+	ReportId       int64  `description:"报告id"`
+	HeadImg        string `description:"报告头图地址"`
+	EndImg         string `description:"报告尾图地址"`
+	CanvasColor    string `description:"画布颜色"`
+	HeadResourceId int    `description:"版头资源ID"`
+	EndResourceId  int    `description:"版尾资源ID"`
+}

+ 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
-}
+}

+ 6 - 0
models/system/sys_group.go

@@ -192,6 +192,12 @@ func MultiUpdateGroupSort(items []*GroupSort) (err error) {
 	return
 }
 
+type GroupNode struct {
+	GroupId   int    `json:"groupId"`
+	GroupName string `json:"groupName"`
+	Child     []*GroupNode
+}
+
 func GetGroupByDepartmentId(departmentId int) (list []*SysFullGroup, err error) {
 	sql := `SELECT
 				s.*, g.group_name AS parent_group_name,

+ 60 - 0
models/user_collect_classify.go

@@ -0,0 +1,60 @@
+// @Author gmy 2024/9/21 14:47:00
+package models
+
+import "github.com/beego/beego/v2/client/orm"
+
+/**
+CREATE TABLE `user_collect_classify` (
+  `user_collect_classify_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `classify_id` int(9) unsigned NOT NULL DEFAULT '0' COMMENT '分类id',
+  `sys_user_id` int(9) unsigned DEFAULT '0' COMMENT '收藏人用户id',
+  `create_time` datetime DEFAULT NULL COMMENT '收藏时间',
+  PRIMARY KEY (`user_collect_classify_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户收藏的分类id';
+*/
+
+type UserCollectClassify struct {
+	UserCollectClassifyId int    `orm:"column(user_collect_classify_id);pk" description:"用户收藏的分类id"`
+	ClassifyId            int    `orm:"column(classify_id)" description:"分类id"`
+	SysUserId             int    `orm:"column(sys_user_id)" description:"收藏人用户id"`
+	CreateTime            string `orm:"column(create_time)" description:"收藏时间"`
+}
+
+// GetUserCollectClassifyList 查询用户收藏的分类列表
+func GetUserCollectClassifyList(sysUserId, classifyId int) (list []*UserCollectClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM user_collect_classify WHERE 1=1`
+	var params []interface{}
+
+	// 处理 sysUserId 参数
+	if sysUserId > 0 {
+		sql += ` AND sys_user_id=?`
+		params = append(params, sysUserId)
+	}
+
+	// 处理 classifyId 参数
+	if classifyId > 0 {
+		sql += ` AND classify_id=?`
+		params = append(params, classifyId)
+	}
+
+	_, err = o.Raw(sql, params...).QueryRows(&list)
+
+	return
+}
+
+// InsertUserCollectClassify 新增用户收藏的分类
+func InsertUserCollectClassify(item UserCollectClassify) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `INSERT INTO user_collect_classify(classify_id, sys_user_id, create_time) VALUES(?, ?, ?)`
+	_, err = o.Raw(sql, item.ClassifyId, item.SysUserId, item.CreateTime).Exec()
+	return
+}
+
+// DeleteUserCollectClassify 删除分类
+func DeleteUserCollectClassify(sysUserId, classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `DELETE FROM user_collect_classify WHERE sys_user_id=? AND classify_id=?`
+	_, err = o.Raw(sql, sysUserId, classifyId).Exec()
+	return
+}

+ 5 - 4
models/wechat_send_msg.go

@@ -15,7 +15,7 @@ func GetOpenIdArr() (items []string, err error) {
 	return
 }
 
-func GetOpenIdArrByClassifyNameSecond(classifyNameSecond string) (items []string, err error) {
+func GetOpenIdArrByClassifyId(classifyId int) (items []string, err error) {
 	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
 			INNER JOIN company AS c ON c.company_id = wu.company_id 
 			INNER JOIN company_product AS d ON c.company_id=d.company_id
@@ -23,10 +23,11 @@ func GetOpenIdArrByClassifyNameSecond(classifyNameSecond string) (items []string
 			INNER JOIN company_report_permission AS e ON d.company_id=e.company_id
 			INNER JOIN chart_permission AS f ON e.chart_permission_id=f.chart_permission_id
 			INNER JOIN chart_permission_search_key_word_mapping AS g ON f.chart_permission_id=g.chart_permission_id
-			WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续')
+			WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续') AND  e.status IN('正式','试用','永续') 
 			AND g.from='rddp'
-			AND g.key_word=?
+			AND g.classify_id=?
 			ORDER BY FIELD(c.company_id, 16) DESC, ur.user_record_id ASC  `
-	_, err = orm.NewOrmUsingDB("weekly").Raw(sql, classifyNameSecond).QueryRows(&items)
+	o := orm.NewOrmUsingDB("weekly")
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
 	return
 }

+ 665 - 26
routers/commentsRouter.go

@@ -763,6 +763,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetBalanceChartList",
+            Router: `/excel_info/balance/chart_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "BalanceVersionList",
+            Router: `/excel_info/balance/version`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
         beego.ControllerComments{
             Method: "GetBaseEdbInfo",
@@ -772,6 +790,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetChildTable",
+            Router: `/excel_info/child_table`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
         beego.ControllerComments{
             Method: "Copy",
@@ -880,6 +907,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/excel_info/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
         beego.ControllerComments{
             Method: "BatchRefresh",
@@ -1600,6 +1636,150 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "BaseInfoEdit",
+            Router: `/chart_info/base/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Copy",
+            Router: `/chart_info/copy`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/chart_info/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "DetailFromUniqueCode",
+            Router: `/chart_info/detail/from_unique_code`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/chart_info/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Preview",
+            Router: `/chart_info/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/chart_info/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoSave",
+            Router: `/chart_info/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/chart_info/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "AddChartClassify",
+            Router: `/chart_classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassify",
+            Router: `/chart_classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassifyCheck",
+            Router: `/chart_classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "EditChartClassify",
+            Router: `/chart_classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyItems",
+            Router: `/chart_classify/items`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyList",
+            Router: `/chart_classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/chart_classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/supply_analysis:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/supply_analysis:VarietyController"],
         beego.ControllerComments{
             Method: "Add",
@@ -1888,6 +2068,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",
@@ -3391,6 +3580,96 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentClassifyList",
+            Router: `/document/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentCollectClassify",
+            Router: `/document/collect/classify`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentDelete",
+            Router: `/document/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentDetail",
+            Router: `/document/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentReportList",
+            Router: `/document/report/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentRuiSiDetail",
+            Router: `/document/rui/si/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "RuiSiReportList",
+            Router: `/document/rui/si/report/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentSave",
+            Router: `/document/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentUpdate",
+            Router: `/document/update`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentVarietyList",
+            Router: `/document/variety/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/english_report:EnPermissionController"],
         beego.ControllerComments{
             Method: "List",
@@ -3652,6 +3931,42 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"],
+        beego.ControllerComments{
+            Method: "CommonChartInfoDetailFromUniqueCode",
+            Router: `/chart/from_unique_code`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"],
+        beego.ControllerComments{
+            Method: "UserChartList",
+            Router: `/chart_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"],
+        beego.ControllerComments{
+            Method: "UserCollectChartList",
+            Router: `/collect/chart`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_forum:EtaForumController"],
+        beego.ControllerComments{
+            Method: "UserCollectChartClassifyList",
+            Router: `/collect/chart_classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_trial:EtaTrialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/eta_trial:EtaTrialController"],
         beego.ControllerComments{
             Method: "QuestionnaireCommit",
@@ -3688,27 +4003,180 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveController"],
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
         beego.ControllerComments{
-            Method: "CheckApproveOpen",
-            Router: `/classify/check_open`,
+            Method: "BatchAdd",
+            Router: `/batch/add`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"],
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
         beego.ControllerComments{
-            Method: "Add",
-            Router: `/flow/add`,
+            Method: "BatchChangeClassify",
+            Router: `/batch/changeClassify`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"],
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
         beego.ControllerComments{
-            Method: "Detail",
+            Method: "BatchDelete",
+            Router: `/batch/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "BatchDownload",
+            Router: `/batch/download`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "ChangeClassify",
+            Router: `/changeClassify`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "AddMaterialClassify",
+            Router: `/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "DeleteMaterialClassify",
+            Router: `/classify/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "DeleteMaterialClassifyCheck",
+            Router: `/classify/del/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "EditMaterialClassify",
+            Router: `/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "MaterialClassifyList",
+            Router: `/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "ClassifyMove",
+            Router: `/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "Download",
+            Router: `/download`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "MyChartSaveAsMaterial",
+            Router: `/my_chart/saveAs`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "Rename",
+            Router: `/rename`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/material:MaterialController"],
+        beego.ControllerComments{
+            Method: "SaveAsMaterial",
+            Router: `/saveAs`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "CheckApproveOpen",
+            Router: `/classify/check_open`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/flow/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "Detail",
             Router: `/flow/detail`,
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
@@ -4327,6 +4795,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyPermission",
+            Router: `/permission_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:CompanyPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:CompanyPermissionController"],
         beego.ControllerComments{
             Method: "List",
@@ -5056,10 +5533,10 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportCommonController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportCommonController"],
         beego.ControllerComments{
-            Method: "CheckDayWeekReportChapterVideo",
-            Router: `/CheckDayWeekReportChapterVideo`,
+            Method: "ShareTransform",
+            Router: `/share/link`,
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -5067,17 +5544,17 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "Add",
-            Router: `/add`,
-            AllowHTTPMethods: []string{"post"},
+            Method: "CheckDayWeekReportChapterVideo",
+            Router: `/CheckDayWeekReportChapterVideo`,
+            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: "AddDayWeekReport",
-            Router: `/addDayWeekReport`,
+            Method: "Add",
+            Router: `/add`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -5110,6 +5587,78 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "AddChapter",
+            Router: `/chapter/add`,
+            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: "EditChapterBaseInfoAndPermission",
+            Router: `/chapter/base_info/edit`,
+            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: "DelChapter",
+            Router: `/chapter/del`,
+            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: "ChapterMove",
+            Router: `/chapter/move`,
+            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: "CancelPublishReportChapter",
+            Router: `/chapter/publish/cancel`,
+            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: "EditChapterTitle",
+            Router: `/chapter/title/edit`,
+            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: "GetUnPublishReportChapterList",
+            Router: `/chapter/un_publish/list`,
+            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: "VoiceUpload",
+            Router: `/chapter/voice/upload`,
+            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: "ClassifyIdDetail",
@@ -5137,6 +5686,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: "BaseDetail",
+            Router: `/detail/base`,
+            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: "Edit",
@@ -5272,6 +5830,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: "EditLayoutImg",
+            Router: `/layout_img/edit`,
+            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: "ListReport",
@@ -5281,6 +5848,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: "AuthorizedListReport",
+            Router: `/list/authorized`,
+            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: "MarkEditStatus",
@@ -5310,7 +5886,7 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "PublishCancleReport",
+            Method: "PublishCancelReport",
             Router: `/publish/cancle`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
@@ -5389,6 +5965,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",
@@ -5416,19 +6001,28 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceController"],
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceAuthController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceAuthController"],
         beego.ControllerComments{
-            Method: "Upload",
-            Router: `/image/upload`,
-            AllowHTTPMethods: []string{"post"},
+            Method: "FileDownload",
+            Router: `/file/download`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceAuthController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceAuthController"],
+        beego.ControllerComments{
+            Method: "OssSTSToken",
+            Router: `/oss/get_sts_token`,
+            AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceController"],
         beego.ControllerComments{
-            Method: "UploadV2",
-            Router: `/image/uploadV2`,
+            Method: "Upload",
+            Router: `/image/upload`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -5436,9 +6030,9 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:ResourceController"],
         beego.ControllerComments{
-            Method: "OssSTSToken",
-            Router: `/oss/get_sts_token`,
-            AllowHTTPMethods: []string{"get"},
+            Method: "UploadV2",
+            Router: `/image/uploadV2`,
+            AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
@@ -5506,6 +6100,51 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/department/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/department/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/department/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "ListDepartment",
+            Router: `/department/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysDepartmentController"],
+        beego.ControllerComments{
+            Method: "DepartmentUserTree",
+            Router: `/department/user_tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysMenuController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers:SysMenuController"],
         beego.ControllerComments{
             Method: "SysMenuList",

+ 33 - 0
routers/router.go

@@ -17,11 +17,15 @@ import (
 	future_good2 "eta/eta_mobile/controllers/data_manage/future_good"
 	"eta/eta_mobile/controllers/data_manage/line_equation"
 	"eta/eta_mobile/controllers/data_manage/line_feature"
+	"eta/eta_mobile/controllers/data_manage/range_analysis"
 	"eta/eta_mobile/controllers/data_manage/supply_analysis"
 	"eta/eta_mobile/controllers/data_source"
 	"eta/eta_mobile/controllers/data_stat"
+	"eta/eta_mobile/controllers/document_manage"
 	"eta/eta_mobile/controllers/english_report"
+	"eta/eta_mobile/controllers/eta_forum"
 	"eta/eta_mobile/controllers/eta_trial"
+	"eta/eta_mobile/controllers/material"
 	"eta/eta_mobile/controllers/report_approve"
 	"eta/eta_mobile/controllers/roadshow"
 	"eta/eta_mobile/controllers/sandbox"
@@ -29,6 +33,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"
 )
@@ -42,6 +48,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(
@@ -120,6 +128,9 @@ func init() {
 		),
 
 		web.NSNamespace("/system",
+			web.NSInclude(
+				&controllers.SysDepartmentController{},
+			),
 			web.NSInclude(
 				&controllers.SysAdminController{},
 			),
@@ -139,6 +150,7 @@ func init() {
 		web.NSNamespace("/resource",
 			web.NSInclude(
 				&controllers.ResourceController{},
+				&controllers.ResourceAuthController{},
 			),
 		),
 		web.NSNamespace("/datamanage",
@@ -304,6 +316,27 @@ func init() {
 				&speech_recognition.SpeechRecognitionTagMenuController{},
 			),
 		),
+		web.NSNamespace("/range_analysis",
+			web.NSInclude(
+				&range_analysis.RangeChartClassifyController{},
+				&range_analysis.RangeChartChartInfoController{},
+			),
+		),
+		web.NSNamespace("/document_manage",
+			web.NSInclude(
+				&document_manage.DocumentManageController{},
+			),
+		),
+		web.NSNamespace("/material",
+			web.NSInclude(
+				&material.MaterialController{},
+			),
+		),
+		web.NSNamespace("/eta_forum",
+			web.NSInclude(
+				&eta_forum.EtaForumController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 10 - 8
services/chart_permission_sync.go

@@ -96,11 +96,12 @@ func ChartFiccPermissionSync() (err error, errMsg string) {
 }
 
 type EditClassifyPermissionReq struct {
-	Keyword string
+	Keyword    string
+	ClassifyId int
 }
 
 // EditClassifyChartPermissionSync 设置报告分类权限
-func EditClassifyChartPermissionSync(keyword string) (err error) {
+func EditClassifyChartPermissionSync(keyword string, classifyId int) (err error) {
 	defer func() {
 		if err != nil {
 			utils.FileLog.Info("同步设置报告分类权限失败, Err: " + err.Error())
@@ -110,7 +111,7 @@ func EditClassifyChartPermissionSync(keyword string) (err error) {
 	if utils.CrmEtaServerUrl == "" {
 		return
 	}
-	req := &EditClassifyPermissionReq{Keyword: keyword}
+	req := &EditClassifyPermissionReq{Keyword: keyword, ClassifyId: classifyId}
 	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/classify/sync")
 	b, err := crmEtaPost(url, req)
 	if err != nil {
@@ -134,10 +135,11 @@ func EditClassifyChartPermissionSync(keyword string) (err error) {
 type EditReportPermissionSyncReq struct {
 	ReportId           int64  `description:"报告id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类ID"`
 }
 
 // EditReportPermissionSync 设置报告权限
-func EditReportPermissionSync(reportId int64, classifyNameSecond string) (err error) {
+func EditReportPermissionSync(reportId int64, classifyIdSecond int) (err error) {
 	defer func() {
 		if err != nil {
 			utils.FileLog.Info("同步设置报告权限失败, Err: " + err.Error())
@@ -147,7 +149,7 @@ func EditReportPermissionSync(reportId int64, classifyNameSecond string) (err er
 	if utils.CrmEtaServerUrl == "" {
 		return
 	}
-	req := &EditReportPermissionSyncReq{ReportId: reportId, ClassifyNameSecond: classifyNameSecond}
+	req := &EditReportPermissionSyncReq{ReportId: reportId, ClassifyIdSecond: classifyIdSecond}
 	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/report/sync")
 	b, err := crmEtaPost(url, req)
 	if err != nil {
@@ -170,11 +172,11 @@ func EditReportPermissionSync(reportId int64, classifyNameSecond string) (err er
 
 type EditKeywordPermissionSyncReq struct {
 	NewKeyword string
-	Keyword    string
+	ClassifyId int
 }
 
 // EditKeywordPermissionSync 设置报告权限分类名称
-func EditKeywordPermissionSync(newKeyword, keyword string) (err error) {
+func EditKeywordPermissionSync(newKeyword string, classifyId int) (err error) {
 	defer func() {
 		if err != nil {
 			utils.FileLog.Info("同步设置报告权限分类名称失败, Err: " + err.Error())
@@ -184,7 +186,7 @@ func EditKeywordPermissionSync(newKeyword, keyword string) (err error) {
 	if utils.CrmEtaServerUrl == "" {
 		return
 	}
-	req := &EditKeywordPermissionSyncReq{NewKeyword: newKeyword, Keyword: keyword}
+	req := &EditKeywordPermissionSyncReq{NewKeyword: newKeyword, ClassifyId: classifyId}
 	url := fmt.Sprint(utils.CrmEtaServerUrl, "/api/crm/chart_permission/keyword/sync")
 	b, err := crmEtaPost(url, req)
 	if err != nil {

+ 849 - 0
services/classify.go

@@ -0,0 +1,849 @@
+package services
+
+import (
+	"errors"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/report_approve"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"sort"
+	"time"
+)
+
+// MoveReportClassify 移动分类
+func MoveReportClassify(req models.ClassifyMoveReq) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		classifyInfo *models.Classify
+		prevClassify *models.Classify
+		nextClassify *models.Classify
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	classifyInfo, err = models.GetClassifyById(classifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在, 请刷新页面"
+			err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	} else if classifyInfo.Id == 0 {
+		errMsg = "分类不存在, 请刷新页面"
+		err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+
+	parentClassifyId := classifyInfo.ParentId
+	if prevClassifyId > 0 {
+		prevClassify, err = models.GetClassifyById(prevClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "上一个分类不存在, 请刷新页面"
+				err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		if prevClassify.ParentId != parentClassifyId {
+			errMsg = "禁止拖动到其他节点"
+			err = fmt.Errorf(errMsg)
+			return
+		}
+		prevSort = prevClassify.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = models.GetClassifyById(nextClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "下一个分类不存在, 请刷新页面"
+				err = fmt.Errorf("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		if nextClassify.ParentId != parentClassifyId {
+			errMsg = "禁止拖动到其他节点"
+			err = fmt.Errorf(errMsg)
+			return
+		}
+		nextSort = nextClassify.Sort
+	}
+
+	err, errMsg = moveReportClassify(classifyInfo, prevClassify, nextClassify, parentClassifyId, prevSort, nextSort)
+	return
+}
+
+// moveReportClassify 移动分类
+func moveReportClassify(classifyInfo, prevClassify, nextClassify *models.Classify, parentId, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(models.Classify)
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if classifyInfo.ParentId != parentId {
+		errMsg = "移动失败"
+		err = fmt.Errorf("不支持目录层级变更")
+		return
+	}
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == classifyInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevClassify != nil {
+					_ = models.UpdateClassifySortByParentId(parentId, prevClassify.Id, prevClassify.Sort, updateSortStr)
+				} else {
+					_ = models.UpdateClassifySortByParentId(parentId, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = models.UpdateClassifySortByParentId(parentId, prevClassify.Id, prevSort, updateSortStr)
+					} else {
+						_ = models.UpdateClassifySortByParentId(parentId, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		classifyInfo.Sort = prevSort + 1
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else if prevClassify == nil && nextClassify == nil && parentId > 0 {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByParentId(parentId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		classifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstPermission, tmpErr := ob.GetFirstClassifyByParentId(parentId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstPermission != nil && firstPermission.Id != 0 && firstPermission.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = models.UpdateClassifySortByParentId(parentId, firstPermission.Id-1, 0, updateSortStr)
+		}
+
+		classifyInfo.Sort = 0 //那就是排在第一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = classifyInfo.UpdateClassify(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}
+
+// AddReportClassify
+// @Description: 添加报告分类
+// @author: Roc
+// @datetime 2024-06-17 11:01:21
+// @param classifyName string
+// @param parentId int
+// @param chartPermissionIdList []int
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func AddReportClassify(classifyName string, parentId int, chartPermissionIdList []int) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	errMsg = `添加失败`
+	item, err := models.GetClassifyByName(classifyName, parentId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取分类信息失败"
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类名称:" + classifyName + "已存在"
+			isSendEmail = false
+		}
+		return
+	}
+	if item != nil {
+		errMsg = "分类名称:" + classifyName + "已存在"
+		isSendEmail = false
+		err = errors.New(errMsg)
+		return
+	}
+
+	level := 1
+
+	// 父级分类
+	var parentClassifyItem *models.Classify
+	// 父级分类下的子分类数量
+	var childClassifyCount int
+
+	if parentId > 0 {
+		// 获取父级分类信息
+		parentClassifyItem, err = models.GetClassifyById(parentId)
+		if err != nil {
+			errMsg = "获取父级分类信息失败"
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "父级分类不存在"
+			}
+			return
+		}
+		level = parentClassifyItem.Level + 1
+
+		if level > 3 {
+			errMsg = "分类层级不可超过三级"
+			isSendEmail = false
+			return
+		}
+
+		// 判断是否分类存在待操作的审批单
+		err, errMsg = checkClassifyApprove(parentClassifyItem)
+		if err != nil {
+			return
+		}
+
+		// 获取父级分类下的子分类数量
+		childClassifyCount, err = models.GetCountClassifyChildByParentId(parentId)
+		if err != nil {
+			errMsg = "获取父级分类的子分类信息失败"
+			return
+		}
+
+	}
+
+	nowTime := time.Now().Local()
+	classify := new(models.Classify)
+
+	maxSort, err := classify.GetMaxSort()
+	if err != nil {
+		errMsg = "操作失败"
+		err = errors.New("查询品种排序失败, Err: " + err.Error())
+		return
+	}
+	classify.ClassifyName = classifyName
+	classify.ParentId = parentId
+	classify.CreateTime = nowTime
+	classify.ModifyTime = nowTime
+	classify.Sort = maxSort + 1
+	classify.Enabled = 1
+	classify.ShowType = 1 //默认列表格式
+	classify.IsShow = 1
+	classify.Level = level
+	/*classify.Abstract = req.Abstract
+	classify.Descript = req.Descript
+	classify.Abstract = req.Abstract
+	classify.Descript = req.Descript
+	classify.ReportAuthor = req.ReportAuthor
+	classify.AuthorDescript = req.AuthorDescript
+	classify.ColumnImgUrl = req.ColumnImgUrl
+	classify.ReportImgUrl = req.ReportImgUrl
+	classify.HeadImgUrl = req.HeadImgUrl
+	classify.AvatarImgUrl = req.AvatarImgUrl
+	classify.HomeImgUrl = req.HomeImgUrl
+	classify.ClassifyLabel = req.ClassifyLabel
+	classify.ShowType = req.ShowType
+	classify.HasTeleconference = req.HasTeleconference
+	classify.VipTitle = req.VipTitle
+
+	classify.IsShow = req.IsShow
+	classify.YbFiccSort = req.YbFiccSort
+	classify.YbFiccIcon = req.YbFiccIcon
+	classify.YbFiccPcIcon = req.YbFiccPcIcon
+	classify.YbIconUrl = req.YbIconUrl
+	classify.YbBgUrl = req.YbBgUrl
+	classify.YbListImg = req.YbListImg
+	classify.YbShareBgImg = req.YbShareBgImg
+	classify.YbRightBanner = req.YbRightBanner
+	classify.RelateTel = req.RelateTel
+	classify.RelateVideo = req.RelateVideo
+	if req.ParentId > 0 {
+		parentClassify := new(models.Classify)
+		if parentClassify, err = models.GetClassifyById(req.ParentId); err != nil {
+			br.Msg = "获取父级分类信息失败"
+			br.ErrMsg = "获取父级分类信息失败, Err:" + err.Error()
+			return
+		}
+		updateParent := false
+		updateCols := make([]string, 0)
+		updateCols = append(updateCols, "HasTeleconference")
+		if req.HasTeleconference == 1 {
+			// 二级分类包含电话会,则一级分类也默认包含电话会
+			if parentClassify.HasTeleconference == 0 {
+				parentClassify.HasTeleconference = 1
+				updateParent = true
+			}
+		} else {
+			// 二级分类均无电话会,则一级分类也无电话会
+			if parentClassify.HasTeleconference == 1 {
+				child, err := models.GetClassifyChild(parentClassify.Id, "")
+				if err != nil {
+					br.Msg = "获取子分类失败"
+					br.ErrMsg = "获取子分类失败,Err:" + err.Error()
+					return
+				}
+				// 存在同一级分类下的二级分类有电话会则不变动
+				hasTel := false
+				for i := 0; i < len(child); i++ {
+					if child[i].HasTeleconference == 1 {
+						hasTel = true
+						break
+					}
+				}
+				if !hasTel {
+					parentClassify.HasTeleconference = 0
+					updateParent = true
+				}
+			}
+		}
+		if updateParent {
+			if err = parentClassify.UpdateClassify(updateCols); err != nil {
+				br.Msg = "更新父级分类失败"
+				br.ErrMsg = "更新父级分类失败, Err:" + err.Error()
+				return
+			}
+		}
+	}*/
+	err = models.AddClassify(classify)
+	if err != nil {
+		return
+	}
+
+	//获取报告分类权限列表
+	err = models.EditChartPermissionSearchKeyWordMappingMulti(classifyName, chartPermissionIdList, classify.Id)
+	if err != nil {
+		errMsg = "修改分类权限失败"
+		return
+	}
+
+	// 修改CRM权限
+	go func() {
+		_ = EditClassifyChartPermissionSync(classifyName, classify.Id)
+	}()
+
+	// 如果父级分类不为空的话,那么就标记有子级分类,同时
+	if parentClassifyItem != nil {
+		parentClassifyItem.HasChild = 1
+		parentClassifyItem.UpdateClassify([]string{"HasChild"})
+
+		// 如果以前没有子级分类,那么就继承父级分类下的章节类型(创建新的章节与分类的关系)
+		if childClassifyCount <= 0 {
+			// 继承父级分类下的章节类型(创建新的章节与分类的关系)
+			tmpErr := inheritReportChapterType(parentId, classify.Id)
+			if tmpErr != nil {
+				return
+			}
+
+			// 继承父级分类审批流
+			go inheritReportApproveFlow(parentClassifyItem, classify)
+
+			moveReportByAddClassify(parentClassifyItem, classify)
+		}
+	}
+
+	return
+}
+
+// checkClassifyApprove
+// @Description: 判断分类是否存在待操作的审批单
+// @author: Roc
+// @datetime 2024-06-27 13:19:15
+// @param currClassify *models.Classify
+// @return err error
+// @return errMsg string
+func checkClassifyApprove(currClassify *models.Classify) (err error, errMsg string) {
+	errMsg = `判断是否有审批流关联失败`
+	var firstClassifyId, secondClassifyId int
+	if currClassify.ParentId > 0 {
+		parentClassifyItem, tmpErr := models.GetClassifyById(currClassify.ParentId)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "获取父级分类信息失败"
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "父级分类不存在"
+			}
+			return
+		}
+		firstClassifyId = parentClassifyItem.Id
+		secondClassifyId = currClassify.Id
+	} else {
+		firstClassifyId = currClassify.Id
+	}
+	// 校验审批流是否关联了进行中的审批
+	{
+		flowOb := new(report_approve.ReportApproveFlow)
+
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId)
+		existPars := make([]interface{}, 0)
+		existPars = append(existPars, report_approve.FlowReportTypeChinese, firstClassifyId, secondClassifyId, 0)
+		flowItem, e := flowOb.GetItemByCondition(existCond, existPars, "")
+		if e != nil {
+			// 父级分类如果没有审批流,那么就正常进行就好了
+			if e.Error() != utils.ErrNoRow() {
+				err = errors.New("获取审批流是否已存在失败, Err: " + e.Error())
+				return
+			}
+			err = nil
+			return
+		}
+		if flowItem == nil {
+			return
+		}
+
+		approvingOb := new(report_approve.ReportApprove)
+		approvingCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveCols.FlowId, report_approve.ReportApproveCols.FlowVersion, report_approve.ReportApproveCols.State)
+		approvingPars := make([]interface{}, 0)
+		approvingPars = append(approvingPars, flowItem.ReportApproveFlowId, flowItem.CurrVersion, report_approve.ReportApproveStateApproving)
+		count, e := approvingOb.GetCountByCondition(approvingCond, approvingPars)
+		if e != nil {
+			err = errors.New("获取审批流关联进行中的审批数失败. Err: " + e.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "当前有未走完流程的报告,请走完流程后再做变更"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	return
+}
+
+// 关于研报分类,因只允许报告或品种关联在最小分类下,所以当某个父分类(非三级分类)已关联报告或品种,需要在该父分类下增加子分类,则第一个子分类添加成功时,默认把该父分类关联的品种和报告转移至新创建的子分类(第一个子分类)下
+// moveReportByAddClassify
+// @Description: 报告和章节的转移
+// @author: Roc
+// @datetime 2024-06-17 16:29:56
+// @param parentClassifyInfo *models.Classify
+// @param currClassifyInfo *models.Classify
+// @return err error
+func moveReportByAddClassify(parentClassifyInfo, currClassifyInfo *models.Classify) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprint("历史报告更改分类失败,父级分类ID:", parentClassifyInfo.Id, ";当前分类ID:", currClassifyInfo.Id, ";错误信息:", err.Error()))
+		}
+	}()
+	if currClassifyInfo.Level > 3 {
+		err = errors.New("父级分类不支持三级分类以上")
+		return
+	}
+
+	// 报告的分类归属调整,转为下一级的分类
+
+	var condition, updateStr string
+	pars := make([]interface{}, 0)
+	switch currClassifyInfo.Level {
+	case 3: // 当前分类是3级分类
+		updateStr += ` classify_id_third = ?,classify_name_third = ?`
+		condition += ` AND classify_id_second = ? `
+	case 2: // 当前分类是2级分类
+		updateStr += ` classify_id_second = ?,classify_name_second = ?`
+		condition += ` AND classify_id_first = ? `
+	default:
+		err = errors.New("错误的分类层级")
+		return
+	}
+
+	pars = append(pars, currClassifyInfo.Id, currClassifyInfo.ClassifyName, parentClassifyInfo.Id)
+
+	// 获取当前分类下的所有章节类型
+	currReportChapterTypeList, err := models.GetAllReportChapterTypeListByClassifyId(currClassifyInfo.Id)
+	if err != nil {
+		return
+	}
+	// 当前的章节类型ID ---> 继承的章节类型ID
+	chapterTypeIdMap := make(map[int]int)
+	for _, v := range currReportChapterTypeList {
+		chapterTypeIdMap[v.ReportChapterTypeId] = v.InheritReportChapterTypeId
+	}
+
+	// 报告转移后,历史章节报告中的type_id也要修复成最新的type_id
+	err = models.ModifyReportClassifyAndReportChapterTypeByCondition(condition, pars, updateStr, chapterTypeIdMap, parentClassifyInfo.Id, currClassifyInfo.Id, currClassifyInfo.ClassifyName)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// inheritReportChapterType
+// @Description: 继承父级分类下的章节类型
+// @author: Roc
+// @datetime 2024-06-17 14:41:04
+// @param parentClassifyId int
+// @param currClassifyId int
+// @return err error
+func inheritReportChapterType(parentClassifyId, currClassifyId int) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprint("继承父级分类下的章节类型失败,父级分类ID:", parentClassifyId, ";当前分类ID:", currClassifyId, ";错误信息:", err.Error()))
+		}
+	}()
+	parentReportChapterTypeList, err := models.GetAllReportChapterTypeListByClassifyId(parentClassifyId)
+	if err != nil {
+		return
+	}
+
+	// 如果没有章节类型,那么就直接返回
+	if len(parentReportChapterTypeList) <= 0 {
+		return
+	}
+
+	addList := make([]*models.ReportChapterType, 0)
+	for _, v := range parentReportChapterTypeList {
+		addList = append(addList, &models.ReportChapterType{
+			//ReportChapterTypeId:        0,
+			ReportChapterTypeKey:       v.ReportChapterTypeKey,
+			ReportChapterTypeThumb:     v.ReportChapterTypeThumb,
+			BannerUrl:                  v.BannerUrl,
+			ReportChapterTypeName:      v.ReportChapterTypeName,
+			Sort:                       v.Sort,
+			Enabled:                    v.Enabled,
+			CreatedTime:                time.Now(),
+			LastUpdatedTime:            time.Now(),
+			ResearchType:               v.ResearchType,
+			SelectedImage:              v.SelectedImage,
+			UnselectedImage:            v.UnselectedImage,
+			PcSelectedImage:            v.PcSelectedImage,
+			PcUnselectedImage:          v.PcUnselectedImage,
+			EditImgUrl:                 v.EditImgUrl,
+			TickerTitle:                v.TickerTitle,
+			IsShow:                     v.IsShow,
+			PauseStartTime:             v.PauseStartTime,
+			PauseEndTime:               v.PauseEndTime,
+			IsSet:                      v.IsSet,
+			YbIconUrl:                  v.YbIconUrl,
+			YbBottomIcon:               v.YbBottomIcon,
+			ReportClassifyId:           currClassifyId,
+			InheritReportChapterTypeId: v.ReportChapterTypeId,
+		})
+	}
+
+	obj := models.ReportChapterType{}
+	err = obj.MultiCreate(addList)
+
+	// 修改CRM权限
+	go func() {
+		var syncReq ChapterTypeSyncReq
+		_, _ = ReportChapterTypeSync(&syncReq)
+	}()
+
+	return
+}
+
+// inheritReportApproveFlow
+// @Description: 继承父级分类下的审批流
+// @author: Roc
+// @datetime 2024-06-17 14:41:04
+// @param parentClassifyId int
+// @param currClassifyId int
+// @return err error
+func inheritReportApproveFlow(parentClassifyItem, currClassifyItem *models.Classify) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprint("继承父级分类下的审批流失败,父级分类ID:", parentClassifyItem.Id, ";当前分类ID:", currClassifyItem.Id, ";错误信息:", err.Error()))
+		}
+	}()
+
+	var firstClassify, secondClassify, thirdClassify *models.Classify
+	if parentClassifyItem.ParentId > 0 {
+		// 获取父级分类信息
+		firstClassify, err = models.GetClassifyById(parentClassifyItem.ParentId)
+		if err != nil {
+			return
+		}
+
+		secondClassify = parentClassifyItem
+		thirdClassify = currClassifyItem
+	} else {
+		firstClassify = parentClassifyItem
+		secondClassify = currClassifyItem
+	}
+
+	flowObj := report_approve.ReportApproveFlow{}
+
+	// 获取父级的审批流
+	existCond := fmt.Sprintf(` AND %s = ? AND %s = ? `, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId)
+	existPars := make([]interface{}, 0)
+	existPars = append(existPars, report_approve.FlowReportTypeChinese, firstClassify.Id)
+
+	// 如果这是第三级,那么说明只需要查找第二级的审批配置就好了
+	if thirdClassify != nil {
+		existCond = fmt.Sprintf(`%s AND %s = ?`, existCond, report_approve.ReportApproveFlowCols.ClassifySecondId)
+		existPars = append(existPars, secondClassify.Id)
+	}
+	//if thirdClassify != nil {
+	//	existCond = fmt.Sprintf(`%s AND %s = ?`, existCond, report_approve.ReportApproveFlowCols.ClassifyThirdId)
+	//	existPars = append(existPars, thirdClassify.Id)
+	//}
+
+	parentFlow, err := flowObj.GetItemByCondition(existCond, existPars, "")
+	if err != nil {
+		// 如果没有配置审批流,那么就直接返回
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		}
+		return
+	}
+
+	// 获取父级的审批节点
+	nodeObj := report_approve.ReportApproveNode{}
+	nodeCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveNodeCols.ReportApproveFlowId, report_approve.ReportApproveNodeCols.CurrVersion)
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, parentFlow.ReportApproveFlowId, parentFlow.CurrVersion)
+	parentNodeList, err := nodeObj.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if err != nil {
+		return
+	}
+
+	// 新审批流
+	currFlow := &report_approve.ReportApproveFlow{
+		ReportApproveFlowId: 0,
+		FlowName:            currClassifyItem.ClassifyName,
+		ReportType:          parentFlow.ReportType,
+		ClassifyFirstId:     firstClassify.Id,
+		ClassifySecondId:    secondClassify.Id,
+		//ClassifyThirdId:     0,
+		CurrVersion: 1,
+		Enabled:     1,
+		CreateTime:  time.Now().Local(),
+		ModifyTime:  time.Now().Local(),
+	}
+	if thirdClassify != nil {
+		currFlow.ClassifyThirdId = thirdClassify.Id
+	}
+
+	// 新审批流的节点
+	nodeItems := make([]*report_approve.ReportApproveNode, 0)
+	for _, v := range parentNodeList {
+		n := &report_approve.ReportApproveNode{
+			//ReportApproveNodeId: 0,
+			//ReportApproveFlowId: 0,
+			PrevNodeId:  0,
+			NextNodeId:  0,
+			NodeType:    v.NodeType,
+			ApproveType: v.ApproveType,
+			Users:       v.Users,
+			CurrVersion: 1,
+			CreateTime:  time.Now().Local(),
+		}
+		nodeItems = append(nodeItems, n)
+	}
+
+	// 新增审批流和节点
+	err = flowObj.CreateFlowAndNodes(currFlow, nodeItems)
+	if err != nil {
+		return
+	}
+
+	parentFlow.Enabled = 0
+	err = parentFlow.Update([]string{"Enabled"})
+
+	return
+}
+
+// EditReportClassify
+// @Description: 编辑报告分类
+// @author: Roc
+// @datetime 2024-06-17 13:31:33
+// @param classifyId int
+// @param classifyName string
+// @param chartPermissionIdList []int
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func EditReportClassify(classifyId int, classifyName string, chartPermissionIdList []int) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	errMsg = `修改失败`
+
+	item, err := models.GetClassifyById(classifyId)
+	if err != nil {
+		errMsg = "获取分类信息失败"
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在, 或已被删除"
+			isSendEmail = false
+		}
+		return
+	}
+	originName := item.ClassifyName
+
+	// 重名校验
+	existName, e := models.GetClassifyByName(classifyName, item.ParentId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		errMsg = "获取信息失败"
+		err = errors.New("获取重名分类失败, Err: " + err.Error())
+		return
+	}
+	if existName != nil && existName.Id != item.Id {
+		errMsg = "分类名称:" + classifyName + "已存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	item.ClassifyName = classifyName
+
+	// ETA1.8.3:不允许修改上级分类  2024-6-17 13:21:01
+	//item.ParentId = req.ParentId
+	item.ModifyTime = time.Now().Local()
+	cols := make([]string, 0)
+	cols = append(cols, "ClassifyName", "ModifyTime")
+	err = item.UpdateClassify(cols)
+	if err != nil {
+		return
+	}
+
+	err = models.EditChartPermissionSearchKeyWordMappingMulti(item.ClassifyName, chartPermissionIdList, item.Id)
+	if err != nil {
+		errMsg = "修改分类权限失败"
+		return
+	}
+
+	// 修改CRM权限
+	go func() {
+		_ = EditClassifyChartPermissionSync(item.ClassifyName, item.Id)
+	}()
+
+	// TODO 修改分类的关联品种时,历史报告中关联的品种怎么处理?
+	// 更新报告分类名称/父级分类后
+	go func() {
+		_ = AfterUpdateClassifyNameOrParent(item.Id, item.ParentId, item.ParentId, originName, item.ClassifyName, item.Level)
+	}()
+
+	return
+}
+
+// GetClassifyTreeRecursive 递归获取分类树形结构
+func GetClassifyTreeRecursive(list []*models.ClassifyItem, parentId int) []*models.ClassifyItem {
+	res := make([]*models.ClassifyItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Child = GetClassifyTreeRecursive(list, v.Id)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// GetParentClassifyListByParentIdList
+// @Description: 递归获取父级分类信息,正常来讲只有三次
+// @author: Roc
+// @datetime 2024-06-19 13:23:33
+// @param parentClassifyIdList []int
+// @return list []*models.ClassifyList
+// @return err error
+func GetParentClassifyListByParentIdList(parentClassifyIdList []int) (list []*models.ClassifyList, err error) {
+	num := len(parentClassifyIdList)
+	if num <= 0 {
+		return
+	}
+	list, err = models.GetClassifyListByParentIdList(parentClassifyIdList)
+	if err != nil {
+		return
+	}
+
+	// 是否还有上级
+	{
+		currParentClassifyIdList := make([]int, 0)
+		for _, v := range list {
+			if v.ParentId > 0 {
+				currParentClassifyIdList = append(currParentClassifyIdList, v.ParentId)
+			}
+		}
+
+		if len(currParentClassifyIdList) > 0 {
+			tmpList, tmpErr := GetParentClassifyListByParentIdList(currParentClassifyIdList)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			list = append(tmpList, list...)
+		}
+	}
+
+	return
+}
+
+// GetClassifyListTreeRecursive
+// @Description: 递归获取分类树形结构
+// @author: Roc
+// @datetime 2024-06-19 13:23:28
+// @param list []*models.ClassifyList
+// @param parentId int
+// @return []*models.ClassifyList
+func GetClassifyListTreeRecursive(list []*models.ClassifyList, parentId int) []*models.ClassifyList {
+	res := make([]*models.ClassifyList, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Child = GetClassifyListTreeRecursive(list, v.Id)
+			res = append(res, v)
+		}
+	}
+
+	// 前端的JP需要我这么返回
+	if len(res) <= 0 {
+		res = nil
+	}
+
+	return res
+}
+
+// BySortAndCreateTime 用来排序,先按Sort字段升序排序,若Sort相同,则按照CreateTime字段升序排序。
+type BySortAndCreateTime []*models.ClassifyList
+
+func (a BySortAndCreateTime) Len() int {
+	return len(a)
+}
+
+func (a BySortAndCreateTime) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a BySortAndCreateTime) Less(i, j int) bool {
+	if a[i].Sort == a[j].Sort {
+		return a[i].CreateTime.Before(a[j].CreateTime)
+	}
+	return a[i].Sort < a[j].Sort
+}
+
+// SortClassifyListBySortAndCreateTime sorts the ClassifyList slice by Sort and then CreateTime in ascending order.
+func SortClassifyListBySortAndCreateTime(classifyList []*models.ClassifyList) {
+	sort.Sort(BySortAndCreateTime(classifyList))
+}

+ 592 - 0
services/data/area_graph/processor_business_logic.go

@@ -0,0 +1,592 @@
+package area_graph
+
+import (
+	"errors"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/utils"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
+	"time"
+)
+
+type InterpolateStrategy struct{}
+
+// Deal 空值填充:插值法填充
+func (i *InterpolateStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*data_manage.ChartEdbInfoMapping, standardIndexMap map[string]*data_manage.EdbDataList, startDate string, endDate string) (err error) {
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+
+				// 存放补充数据
+				var replenishDataList []*data_manage.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 插值法补充数据
+				var startEdbInfoData *data_manage.EdbDataList
+				for index := 0; index < len(dataList); index++ {
+					// 获取当前数据和下一个数据
+					currentIndexData := dataList[index]
+					//afterIndexData := dataList[index+1]
+
+					if startEdbInfoData == nil {
+						startEdbInfoData = currentIndexData
+						continue
+					}
+
+					// 获取两条数据之间相差的天数
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+					currDataTime, _ := time.ParseInLocation(utils.FormatDate, currentIndexData.DataTime, time.Local)
+					betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+					betweenDay := betweenHour / 24
+
+					// 如果相差一天,那么过滤
+					if betweenDay <= 1 {
+						startEdbInfoData = currentIndexData
+						continue
+					}
+
+					// 生成线性方程式
+					var a, b float64
+					{
+						coordinateData := make([]utils.Coordinate, 0)
+						tmpCoordinate1 := utils.Coordinate{
+							X: 1,
+							Y: startEdbInfoData.Value,
+						}
+						coordinateData = append(coordinateData, tmpCoordinate1)
+						tmpCoordinate2 := utils.Coordinate{
+							X: float64(betweenDay) + 1,
+							Y: currentIndexData.Value,
+						}
+						coordinateData = append(coordinateData, tmpCoordinate2)
+
+						a, b = utils.GetLinearResult(coordinateData)
+						if math.IsNaN(a) || math.IsNaN(b) {
+							err = errors.New("线性方程公式生成失败")
+							return
+						}
+					}
+
+					// 插值补充数据
+					for i := 1; i < betweenDay; i++ {
+						tmpDataTime := startDataTime.AddDate(0, 0, i)
+						aDecimal := decimal.NewFromFloat(a)
+						xDecimal := decimal.NewFromInt(int64(i) + 1)
+						bDecimal := decimal.NewFromFloat(b)
+
+						val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+						nextDay := tmpDataTime.Format(utils.FormatDate)
+
+						replenishIndexData := data_manage.EdbDataList{
+							EdbDataId:     currentIndexData.EdbDataId,
+							DataTime:      nextDay,
+							DataTimestamp: tmpDataTime.UnixMilli(),
+							Value:         val,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+					}
+					startEdbInfoData = currentIndexData
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*data_manage.EdbDataList
+				for _, dataObject := range dataList {
+					if _, ok := standardIndexMap[dataObject.DataTime]; ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type FillWithPreviousStrategy struct{}
+
+// Deal 空值填充:前值填充
+func (f *FillWithPreviousStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*data_manage.ChartEdbInfoMapping, standardIndexMap map[string]*data_manage.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+				// 存放补充数据
+				var replenishDataList []*data_manage.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := data_manage.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         beforeIndexData.Value,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						beforeIndexData = &replenishIndexData
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*data_manage.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type FillWithNextStrategy struct{}
+
+// Deal 空值填充:后值填充
+func (f *FillWithNextStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*data_manage.ChartEdbInfoMapping, standardIndexMap map[string]*data_manage.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+
+				// 存放补充数据
+				var replenishDataList []*data_manage.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for !startDataTime.After(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				// 将切片数据倒序
+				reverseSlice(dataList)
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(afterIndexData.DataTime, beforeIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(afterIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := data_manage.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         beforeIndexData.Value,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						afterIndexData = &replenishIndexData
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*data_manage.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type SetToZeroStrategy struct{}
+
+// Deal 空值填充:设为0
+func (s *SetToZeroStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*data_manage.ChartEdbInfoMapping, standardIndexMap map[string]*data_manage.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+				// 存放补充数据
+				var replenishDataList []*data_manage.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := data_manage.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         0,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						beforeIndexData = &replenishIndexData
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := data_manage.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*data_manage.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type DeleteDateStrategy struct{}
+
+// Deal 删除日期
+func (d *DeleteDateStrategy) Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*data_manage.ChartEdbInfoMapping, standardIndexMap map[string]*data_manage.EdbDataList, startDate string, endDate string) error {
+	// 取所有指标的时间交集
+	// 创建一个 map 来保存每个时间点的出现次数
+	timeMap := make(map[string]int)
+	for _, v := range edbDataList {
+		if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+			// 遍历所有的 dataList,为每个 DataTime 增加一个计数
+			for _, dataObject := range dataList {
+				timeMap[dataObject.DataTime]++
+			}
+		}
+	}
+
+	for _, v := range edbDataList {
+		if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+			// 遍历所有的 dataList,保留所有时间点在所有指标中都存在的数据
+			var resultDataList []*data_manage.EdbDataList
+			for _, dataObject := range dataList {
+				if timeMap[dataObject.DataTime] == len(edbDataList) {
+					// 如果该时间点在所有指标中都存在,加入到结果列表
+					resultDataList = append(resultDataList, dataObject)
+				}
+			}
+
+			// 将符合条件的数据重新赋值回 v.DataList
+			v.DataList = resultDataList
+		}
+	}
+	return nil
+}
+
+// 将列表颠倒
+func reverseSlice(dataList []*data_manage.EdbDataList) {
+	// 使用双指针法,前后两个指针向中间逼近
+	for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+		// 交换位置
+		dataList[i], dataList[j] = dataList[j], dataList[i]
+	}
+}

+ 27 - 0
services/data/area_graph/processor_factory.go

@@ -0,0 +1,27 @@
+package area_graph
+
+import (
+	"eta/eta_mobile/models/data_manage"
+	"fmt"
+)
+
+type NullDealStrategy interface {
+	Deal(tmpConfig data_manage.AreaExtraConf, edbDataList []*data_manage.ChartEdbInfoMapping, standardIndexMap map[string]*data_manage.EdbDataList, startDate string, endDate string) (err error)
+}
+
+func CreateStrategy(dealWay int) (NullDealStrategy, error) {
+	switch dealWay {
+	case 1:
+		return &InterpolateStrategy{}, nil
+	case 2:
+		return &FillWithPreviousStrategy{}, nil
+	case 3:
+		return &FillWithNextStrategy{}, nil
+	case 4:
+		return &SetToZeroStrategy{}, nil
+	case 5:
+		return &DeleteDateStrategy{}, nil
+	default:
+		return nil, fmt.Errorf("未知的空值处理类型: %d", dealWay)
+	}
+}

+ 26 - 0
services/data/chart_classify.go

@@ -396,3 +396,29 @@ func EditChartClassify(chartClassifyId, source int, chartClassifyName, lang stri
 
 	return
 }
+
+// GetChartClassifyTreeRecursive 递归获取分类树形结构
+func GetChartClassifyTreeRecursive(list []*data_manage.ChartClassifyItems, parentId int) []*data_manage.ChartClassifyItems {
+	res := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = GetChartClassifyTreeRecursive(list, v.ChartClassifyId)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// GetChartClassifyParentRecursive 根据子目录递归父级目录
+func GetChartClassifyParentRecursive(list []*data_manage.ChartClassifyItems, classifyId int) []*data_manage.ChartClassifyItems {
+	res := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range list {
+		if v.ChartClassifyId == classifyId {
+			if v.ParentId > 0 {
+				res = GetChartClassifyParentRecursive(list, v.ParentId)
+			}
+			res = append(res, v)
+		}
+	}
+	return res
+}

+ 52 - 62
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)
@@ -653,6 +662,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = ""
 			item.ChartColor = ""
 			item.ChartWidth = 0
+			item.ChartScale = 0
 			item.MaxData = v.MaxValue
 			item.MinData = v.MinValue
 		} else {
@@ -666,6 +676,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = v.ChartStyle
 			item.ChartColor = v.ChartColor
 			item.ChartWidth = v.ChartWidth
+			item.ChartScale = v.ChartScale
 			item.IsOrder = v.IsOrder
 			item.MaxData = v.MaxData
 			item.MinData = v.MinData
@@ -815,72 +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
-			}
-
 			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.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}
@@ -897,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
@@ -947,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)
 
@@ -998,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) {
 			//如果最新的日期在起始日之前,则跳出循环
@@ -1094,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
@@ -1146,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)
 
@@ -1199,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
@@ -2405,6 +2385,7 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 					ChartColor:        "",
 					PredictChartColor: "",
 					ChartWidth:        0,
+					ChartScale:        0,
 					Source:            utils.CHART_SOURCE_DEFAULT,
 				})
 			}
@@ -2857,6 +2838,8 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin, lang
 		if chartItem.ChartType == 2 && chartItem.DateType <= 0 {
 			dateType = 3
 		}
+	} else {
+		calendar = req.Calendar
 	}
 	sort.Ints(edbInfoIdArr)
 	var edbInfoIdArrStr []string
@@ -3268,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)
@@ -3459,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)
 				}
 			}
@@ -4237,7 +4227,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 			valueMap := make(map[time.Time]float64)
 
 			samePeriodStandardDeviationList := make([]*data_manage.MaxMinLimitsData, 0)
-			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodStandardDeviation.Year-1 && i > 0; i-- {
 				// 插值成日度
 				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
 				if err != nil {

+ 1 - 1
services/data/chart_info_elastic.go

@@ -125,7 +125,7 @@ func EsDeleteChartInfo(chartInfoId int) {
 }
 
 // EsSearchChartInfo 搜索图表信息
-func EsSearchChartInfo(keyword string, showSysId int, sourceList []int, noPermissionChartIdList []int, startSize, pageSize int) (list []*data_manage.ChartInfo, total int64, err error) {
+func EsSearchChartInfo(keyword string, showSysId int, sourceList []int, noPermissionChartIdList []int, startSize, pageSize int) (list []*data_manage.ChartInfoMore, total int64, err error) {
 	list, total, err = elastic.SearchChartInfoData(utils.CHART_INDEX_NAME, keyword, showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
 	return
 }

+ 19 - 45
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,72 +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
-				}
-			}
-
 			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.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

+ 122 - 0
services/data/edb_info_relation.go

@@ -0,0 +1,122 @@
+package data
+
+import (
+	"eta/eta_mobile/models/data_manage"
+	"fmt"
+)
+
+// 查询两个指标是否存在循环引用关系
+func CheckTwoEdbInfoRelation(edbInfoA, edbInfoB *data_manage.EdbInfo) (hasRelation bool, err error) {
+	//查询指标信息
+	allEdbMappingMap := make(map[int][]*data_manage.EdbInfoCalculateMappingInfo, 0)
+	allMappingList, e := data_manage.GetEdbInfoCalculateMappingListByEdbInfoId(edbInfoA.EdbInfoId)
+	if e != nil {
+		err = fmt.Errorf("GetEdbInfoCalculateMappingListByEdbInfoIds err: %s", e.Error())
+		return
+	}
+	for _, v := range allMappingList {
+		if v.EdbInfoId == edbInfoB.EdbInfoId {
+			hasRelation = true
+			return
+		}
+		if _, ok := allEdbMappingMap[v.EdbInfoId]; !ok {
+			allEdbMappingMap[v.EdbInfoId] = make([]*data_manage.EdbInfoCalculateMappingInfo, 0)
+		}
+		allEdbMappingMap[v.EdbInfoId] = append(allEdbMappingMap[v.EdbInfoId], v)
+	}
+	//查询指标映射
+	//查询所有指标数据
+	//查询这个指标相关的mapping信息放到数组里,
+	//将得到的指标ID信息放到数组里
+	hasFindMap := make(map[int]struct{})
+	edbInfoIdMap := make(map[int]struct{})
+	edbMappingList := make([]*data_manage.EdbInfoCalculateMapping, 0)
+	edbInfoMappingRootIdsMap := make(map[int][]int, 0)
+	edbMappingMap := make(map[int]struct{})
+
+	if edbInfoA.EdbType == 2 {
+		edbInfoId := edbInfoA.EdbInfoId
+		edbMappingList, err = getCalculateEdbInfoByEdbInfoId(allEdbMappingMap, edbInfoId, hasFindMap, edbInfoIdMap, edbMappingList, edbMappingMap, edbInfoMappingRootIdsMap, edbInfoId)
+		if err != nil {
+			err = fmt.Errorf(" GetCalculateEdbInfoByEdbInfoId err: %s", err.Error())
+			return
+		}
+		// 判断其中是否包含指标B
+		if _, ok := edbInfoIdMap[edbInfoB.EdbInfoId]; ok { // 如果包含,则说明存在循环引用关系
+			hasRelation = true
+			return
+		}
+	}
+
+	return
+}
+
+// getCalculateEdbInfoByEdbInfoId 计算指标追溯
+func getCalculateEdbInfoByEdbInfoId(allEdbMappingMap map[int][]*data_manage.EdbInfoCalculateMappingInfo, edbInfoId int, hasFindMap map[int]struct{}, edbInfoIdMap map[int]struct{}, edbMappingList []*data_manage.EdbInfoCalculateMapping, edbMappingMap map[int]struct{}, edbInfoMappingRootIdsMap map[int][]int, rootEdbInfoId int) (newEdbMappingList []*data_manage.EdbInfoCalculateMapping, err error) {
+	newEdbMappingList = edbMappingList
+	_, ok := hasFindMap[edbInfoId]
+	if ok {
+		return
+	}
+
+	if _, ok1 := edbInfoIdMap[edbInfoId]; !ok1 {
+		edbInfoIdMap[edbInfoId] = struct{}{}
+	}
+	edbInfoMappingList := make([]*data_manage.EdbInfoCalculateMappingInfo, 0)
+	edbInfoMappingList, ok = allEdbMappingMap[edbInfoId]
+	if !ok {
+		edbInfoMappingList, err = data_manage.GetEdbInfoCalculateMappingListByEdbInfoId(edbInfoId)
+		if err != nil {
+			err = fmt.Errorf("GetEdbInfoCalculateMappingListByEdbInfoId err: %s", err.Error())
+			return
+		}
+	}
+	hasFindMap[edbInfoId] = struct{}{}
+	if len(edbInfoMappingList) > 0 {
+		fromEdbInfoIdList := make([]int, 0)
+		edbInfoMappingIdList := make([]int, 0)
+		for _, v := range edbInfoMappingList {
+			fromEdbInfoIdList = append(fromEdbInfoIdList, v.FromEdbInfoId)
+			edbInfoMappingIdList = append(edbInfoMappingIdList, v.EdbInfoCalculateMappingId)
+			if _, ok1 := edbInfoIdMap[v.FromEdbInfoId]; !ok1 {
+				edbInfoIdMap[v.FromEdbInfoId] = struct{}{}
+			}
+			if _, ok2 := edbMappingMap[v.EdbInfoCalculateMappingId]; !ok2 {
+				edbMappingMap[v.EdbInfoCalculateMappingId] = struct{}{}
+				tmp := &data_manage.EdbInfoCalculateMapping{
+					EdbInfoCalculateMappingId: v.EdbInfoCalculateMappingId,
+					EdbInfoId:                 v.EdbInfoId,
+					Source:                    v.Source,
+					SourceName:                v.SourceName,
+					EdbCode:                   v.EdbCode,
+					FromEdbInfoId:             v.FromEdbInfoId,
+					FromEdbCode:               v.FromEdbCode,
+					FromEdbName:               v.FromEdbName,
+					FromSource:                v.FromSource,
+					FromSourceName:            v.FromSourceName,
+					FromTag:                   v.FromTag,
+					Sort:                      v.Sort,
+					CreateTime:                v.CreateTime,
+					ModifyTime:                v.ModifyTime,
+				}
+				newEdbMappingList = append(newEdbMappingList, tmp)
+
+			}
+
+			if edbInfoId != v.FromEdbInfoId && (v.FromEdbType == 2 || v.FromEdbInfoType == 1) {
+				// 查过了就不查了
+				if _, ok2 := hasFindMap[v.FromEdbInfoId]; !ok2 {
+					newEdbMappingList, err = getCalculateEdbInfoByEdbInfoId(allEdbMappingMap, v.FromEdbInfoId, hasFindMap, edbInfoIdMap, newEdbMappingList, edbMappingMap, edbInfoMappingRootIdsMap, rootEdbInfoId)
+					if err != nil {
+						err = fmt.Errorf("traceEdbInfoByEdbInfoId err: %s", err.Error())
+						return
+					}
+				}
+			}
+			hasFindMap[v.FromEdbInfoId] = struct{}{}
+		}
+		edbInfoMappingRootIdsMap[rootEdbInfoId] = append(edbInfoMappingRootIdsMap[rootEdbInfoId], edbInfoMappingIdList...)
+	}
+
+	return
+}

+ 66 - 0
services/data/excel/balance_table.go

@@ -406,3 +406,69 @@ func GetBalanceExcelInfoOpButton(sysUserId, parentSysUserId int, haveOperaAuth b
 	}
 	return
 }
+
+func GetBalanceExcelChartList(excelInfo *excelModel.ExcelInfo, lang string) (list []*data_manage.ChartInfoView, mappingListMap map[int][]*excelModel.ExcelChartEdb, dataListMap map[int][]*data_manage.EdbDataList, err error, errMsg string) {
+	dataListMap = make(map[int][]*data_manage.EdbDataList)
+	// 相关联指标
+	mappingListTmp, err := excelModel.GetExcelChartEdbMappingByExcelInfoId(excelInfo.ExcelInfoId)
+	if err != nil {
+		errMsg = "获取失败"
+		err = fmt.Errorf(" 获取图表,指标信息失败 Err:%s", err.Error())
+		return
+	}
+	charInfoIds := make([]int, 0)
+	mappingListMap = make(map[int][]*excelModel.ExcelChartEdb, 0)
+	if excelInfo.BalanceType == 1 {
+		for _, mapping := range mappingListTmp {
+			mappingListMap[mapping.ChartInfoId] = append(mappingListMap[mapping.ChartInfoId], mapping)
+		}
+		//查询库里是否有值
+		chartDataList, e := excelModel.GetExcelChartDataByExcelInfoId(excelInfo.ExcelInfoId)
+		if e != nil {
+			err = fmt.Errorf(" 获取图表,指标信息失败 Err:%s", e.Error())
+			return
+		}
+		if len(chartDataList) > 0 {
+			for _, v := range chartDataList {
+				tmp := &data_manage.EdbDataList{
+					EdbDataId:     v.ExcelChartDataId,
+					EdbInfoId:     v.ExcelChartEdbId,
+					DataTime:      v.DataTime,
+					DataTimestamp: v.DataTimestamp,
+					Value:         v.Value,
+				}
+				dataListMap[v.ExcelChartEdbId] = append(dataListMap[v.ExcelChartEdbId], tmp)
+			}
+		}
+	} else {
+		newExcelDataMap, excelAllRows, excelAllCols, e, msg := GetBalanceExcelData(excelInfo, lang)
+		if e != nil {
+			err = e
+			errMsg = msg
+			return
+		}
+		for _, mapping := range mappingListTmp {
+			mappingListMap[mapping.ChartInfoId] = append(mappingListMap[mapping.ChartInfoId], mapping)
+			er, ms := GetBalanceExcelEdbData(mapping, newExcelDataMap, dataListMap, excelAllRows, excelAllCols)
+			if er != nil {
+				utils.FileLog.Info(fmt.Sprintf(" 获取图表,指标信息失败 Err:%s, %s", er.Error(), ms))
+				continue
+			}
+		}
+	}
+
+	for k, _ := range mappingListMap {
+		charInfoIds = append(charInfoIds, k)
+	}
+	list = make([]*data_manage.ChartInfoView, 0)
+	if len(charInfoIds) > 0 {
+		chartInfoList, e := data_manage.GetChartInfoViewByIdList(charInfoIds)
+		if e != nil {
+			errMsg = "获取失败"
+			err = fmt.Errorf(" 获取图表,指标信息失败 Err:%s", e.Error())
+			return
+		}
+		list = chartInfoList
+	}
+	return
+}

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

@@ -103,6 +103,14 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 		SourcesFrom:     excelInfo.SourcesFrom,
 	}
 
+	// 额外配置(表格冻结行列等)
+	if excelInfo.ExtraConfig != "" {
+		if e := json.Unmarshal([]byte(excelInfo.ExtraConfig), &excelDetail.ExtraConfig); e != nil {
+			err = fmt.Errorf("额外配置解析失败, %v", e)
+			return
+		}
+	}
+
 	// 无权限,不需要返回数据
 	if !haveOperaAuth {
 		return
@@ -161,7 +169,7 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 		excelDetail.ExcelSource = strings.Join(sourceNameList, ",")
 		excelDetail.ExcelSourceEn = strings.Join(sourceNameEnList, ",")
 		excelDetail.TableData = result
-	case utils.MIXED_TABLE: // 混合表格
+	case utils.MIXED_TABLE, utils.BALANCE_TABLE: // 混合表格/平衡表
 		var result request.MixedTableReq
 		err = json.Unmarshal([]byte(excelDetail.Content), &result)
 		if err != nil {
@@ -1456,9 +1464,8 @@ func GetExcelEdbBatchRefreshKey(source string, reportId, chapterId int) string {
 	return fmt.Sprint("batch_refresh_excel_edb:", source, ":", reportId, ":", chapterId)
 }
 
-
 // GetEdbSourceByEdbInfoIdList 获取关联指标的来源
-func GetEdbSourceByEdbInfoIdList(edbInfoIdList []int) (sourceNameList, sourceNameEnList []string,err error) {
+func GetEdbSourceByEdbInfoIdList(edbInfoIdList []int) (sourceNameList, sourceNameEnList []string, err error) {
 	sourceNameList = make([]string, 0)
 	sourceNameEnList = make([]string, 0)
 	sourceMap := make(map[int]string)
@@ -1507,4 +1514,4 @@ func GetEdbSourceByEdbInfoIdList(edbInfoIdList []int) (sourceNameList, sourceNam
 		sourceNameEnList = append(sourceNameEnList, conf[models.BusinessConfCompanyName])
 	}
 	return
-}
+}

+ 3 - 90
services/data/predict_edb_info.go

@@ -913,97 +913,10 @@ func GetPredictDataListByPredictEdbInfoId(edbInfoId int, startDate, endDate stri
 // GetPredictDataListByPredictEdbInfo 根据预测指标信息获取预测指标的数据
 func GetPredictDataListByPredictEdbInfo(edbInfo *data_manage.EdbInfo, startDate, endDate string, isTimeBetween bool) (dataList []*data_manage.EdbDataList, sourceEdbInfoItem *data_manage.EdbInfo, predictEdbConf *data_manage.PredictEdbConf, err error, errMsg string) {
 	// 非计算指标,直接从表里获取数据
-	if edbInfo.EdbType != 1 {
-		if !isTimeBetween { //如果不是区间数据,那么就结束日期为空
-			endDate = ``
-		}
-		return GetPredictCalculateDataListByPredictEdbInfo(edbInfo, startDate, endDate)
-	}
-	// 查找该预测指标配置
-	predictEdbConfList, err := data_manage.GetPredictEdbConfListById(edbInfo.EdbInfoId)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		errMsg = "获取预测指标配置信息失败"
-		return
-	}
-	if len(predictEdbConfList) == 0 {
-		errMsg = "获取预测指标配置信息失败"
-		err = errors.New(errMsg)
-		return
+	if !isTimeBetween { //如果不是区间数据,那么就结束日期为空
+		endDate = ``
 	}
-	predictEdbConf = predictEdbConfList[0]
-
-	// 来源指标
-	sourceEdbInfoItem, err = data_manage.GetEdbInfoById(predictEdbConf.SourceEdbInfoId)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			errMsg = "找不到来源指标信息"
-			err = errors.New(errMsg)
-		}
-		return
-	}
-
-	allDataList := make([]*data_manage.EdbDataList, 0)
-	//获取指标数据(实际已生成)
-	dataList, err = data_manage.GetEdbDataList(sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, sourceEdbInfoItem.EdbInfoId, startDate, endDate)
-	if err != nil {
-		return
-	}
-	// 如果选择了日期,那么需要筛选所有的数据,用于未来指标的生成
-	if startDate != `` {
-		allDataList, err = data_manage.GetEdbDataList(sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, sourceEdbInfoItem.EdbInfoId, "", "")
-		if err != nil {
-			return
-		}
-	} else {
-		allDataList = dataList
-	}
-	// 获取预测指标未来的数据
-	predictDataList := make([]*data_manage.EdbDataList, 0)
-
-	endDateStr := edbInfo.EndDate //预测指标的结束日期
-
-	if isTimeBetween && endDate != `` { //如果是时间区间,同时截止日期不为空的情况,那么
-		reqEndDateTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
-		endDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfo.EndDate, time.Local)
-		// 如果选择的时间区间结束日期 晚于 当天,那么预测数据截止到当天
-		if reqEndDateTime.Before(endDateTime) {
-			endDateStr = endDate
-		}
-	}
-	//predictDataList, err = GetChartPredictEdbInfoDataList(*predictEdbConf, startDate, sourceEdbInfoItem.LatestDate, sourceEdbInfoItem.LatestValue, endDateStr, edbInfo.Frequency)
-	predictEdbConfDataList := make([]data_manage.PredictEdbConfAndData, 0)
-	for _, v := range predictEdbConfList {
-		predictEdbConfDataList = append(predictEdbConfDataList, data_manage.PredictEdbConfAndData{
-			ConfigId:         v.ConfigId,
-			PredictEdbInfoId: v.PredictEdbInfoId,
-			SourceEdbInfoId:  v.SourceEdbInfoId,
-			RuleType:         v.RuleType,
-			FixedValue:       v.FixedValue,
-			Value:            v.Value,
-			EndDate:          v.EndDate,
-			ModifyTime:       v.ModifyTime,
-			CreateTime:       v.CreateTime,
-			DataList:         make([]*data_manage.EdbDataList, 0),
-		})
-	}
-	//var predictMinValue, predictMaxValue float64
-	predictDataList, _, _, err, _ = GetChartPredictEdbInfoDataListByConfList(predictEdbConfDataList, startDate, sourceEdbInfoItem.LatestDate, endDateStr, edbInfo.Frequency, edbInfo.DataDateType, allDataList)
-	if err != nil {
-		return
-	}
-	dataList = append(dataList, predictDataList...)
-	//if len(predictDataList) > 0 {
-	//	// 如果最小值 大于 预测值,那么将预测值作为最小值数据返回
-	//	if edbInfo.MinValue > predictMinValue {
-	//		edbInfo.MinValue = predictMinValue
-	//	}
-	//
-	//	// 如果最大值 小于 预测值,那么将预测值作为最大值数据返回
-	//	if edbInfo.MaxValue < predictMaxValue {
-	//		edbInfo.MaxValue = predictMaxValue
-	//	}
-	//}
-	return
+	return GetPredictCalculateDataListByPredictEdbInfo(edbInfo, startDate, endDate)
 }
 
 // GetChartDataList 通过完整的预测数据 进行 季节性图、公历、农历处理

+ 165 - 103
services/data/predict_edb_info_rule.go

@@ -1525,21 +1525,24 @@ func GetChartPredictEdbInfoDataListByRuleNAnnualAverage(edbInfoId int, configVal
 
 // AnnualValueInversionConf 年度值倒推规则
 type AnnualValueInversionConf struct {
-	Value float64 `description:"年度值"`
-	Type  int     `description:"分配方式,1:均值法;2:同比法"`
-	Year  int     `description:"同比年份"`
+	Value    float64 `description:"年度值"`
+	Type     int     `description:"分配方式,1:均值法;2:同比法"`
+	Year     int     `description:"同比年份"`
+	YearList []int   `description:"指定年份列表"`
 }
 
 // GetChartPredictEdbInfoDataListByRuleAnnualValueInversion 根据 年度值倒推 规则获取预测数据
-// ETA预测规则:年度值倒推:设定年度值,余额=年度值-年初至今累计值(算法参考累计值),进行余额分配,均值法分配时保证每期数值相等(日度/周度:剩余期数=剩余自然日历天数/今年指标最新日期自然日历天数*今年至今指标数据期数;旬度/月度/季度/半年度:剩余期数=全年期数(36\12\4\2)-今年至今自然日历期数),同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速)
-// 举例:
-// 指标A 日度 最新日期 2023-05-19 年初至今累计值100
-// 设置年度值1000
-// 则余额=1000-100=900
-// 均值法分配:剩余期数=226/139*120=195.11
-// 今年之后的每一期预测值=900/195.11=4.6128
-// 同比法分配:同比增速=900/同比年份5.19的余额
-// 预测值=同比年份5-20的值*(1+同比增速)
+// 预测指标-年度值倒推
+// 1、年度值倒推,选择同比法,支持选择多个年份(当前只可选择一个年份)。选择多个年份时,计算多个年份的余额平均,和同期平均。
+// 2、年度值倒推,同比法的算法优化:旬度,月度,季度,半年度的算法,同原先算法。
+// 日度、周度值算法更新(假设指标实际值最新日期月2024/3/1):
+// 1、设定年度值
+// 2、计算余额:年度值-年初至今累计值
+// 3、年初至今累计值计算方法:用后置填充变频成连续自然日日度数据。计算1/1至指标最新日期(2024/3/3/1)的累计值。
+// 4、计算同比年份全年累计值,年初至指标最新值同期(2023/3/1)累计值,两者相减得到同比年份同期余额,再取平均值,作为最终的同期余额
+// 5、用今年余额/去年同期余额得到同比增速。
+// 6、每一期预测值,为同比年份的同期值,乘以(1+同比)。去年同期,用变频后的序列对应。
+// 7、如果选择的同比年份是多个。则计算多个年份的平均余额。今年余额/平均余额=同比增速。同比基数为多个年份的同期平均值
 func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, configValue string, dayList []time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*data_manage.EdbDataList, existMap map[string]float64) (newPredictEdbInfoData []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
 	if frequency == "年度" {
 		err = errors.New("当前指标频度是年度,不允许配置年度值倒推")
@@ -1560,6 +1563,11 @@ func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, con
 	newPredictEdbInfoData = predictEdbInfoData
 	index := len(allDataList)
 
+	// 没有数据,直接返回
+	if index <= 0 {
+		return
+	}
+
 	// 配置的年度值
 	yearValueConfig := annualValueInversionConf.Value
 
@@ -1708,112 +1716,166 @@ func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, con
 	// 同比法分配
 	// 同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速);
 	// 同比法分配:同比增速=900/同比年份5.19的余额
-
+	yearList := annualValueInversionConf.YearList
+	if len(yearList) == 0 {
+		//兼容历史数据
+		yearList = append(yearList, annualValueInversionConf.Year)
+	}
 	// 每年截止到当前日期的累计值
 	dateTotalMap := make(map[time.Time]float64)
+
+	//把每一期的期数和日期绑定
+	dateIndexMap := make(map[time.Time]int)
+	indexDateMap := make(map[int]time.Time)
 	// 每年的累计值(计算使用)
 	yearTotalMap := make(map[int]float64)
-	for _, v := range allDataList {
-		currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
-		if tmpErr != nil {
-			err = tmpErr
-			return
+	//数据按找后值填充的方式处理成连续自然日日度数据
+	allDataListMap := make(map[string]float64)
+	// todo 如果是日度和周度,用后置填充变频成连续自然日日度数据。计算1/1至指标最新日期(2024/3/3/1)的累计值
+	switch frequency {
+	case "日度", "周度":
+		for _, v := range allDataList {
+			allDataListMap[v.DataTime] = v.Value
+		}
+		//找到最早日期的的年份的1月1日,转成time格式
+		earliestYear := allDataList[0].DataTime[:4]
+		earliestYearFirstDay, _ := time.ParseInLocation(utils.FormatDate, earliestYear+"-01-01", time.Local)
+		days := int(currDayTime.Sub(earliestYearFirstDay).Hours() / float64(24))
+		//循环累加日期,直到循环到最新日期
+		for i := 0; i <= days; i++ {
+			currentDate := earliestYearFirstDay.AddDate(0, 0, i)
+			currentDateStr := currentDate.Format(utils.FormatDate)
+			val, ok := allDataListMap[currentDateStr]
+			if !ok { //如果不存在,则填充后值
+				//循环向后查找数据,直到找到
+				for j := i + 1; j <= days; j++ {
+					//循环往后取值
+					currentDateTmp := earliestYearFirstDay.AddDate(0, 0, j)
+					currentDateTmpStr := currentDateTmp.Format(utils.FormatDate)
+					if tmpVal, ok1 := allDataListMap[currentDateTmpStr]; ok1 {
+						allDataListMap[currentDateStr] = tmpVal
+						val = tmpVal
+						break
+					}
+				}
+			}
+			//计算每一天的年初至今累计值
+			yearVal := yearTotalMap[currentDate.Year()]
+			if frequency == "周度" {
+				// 每日累计值需要当前值除7
+				yearVal = yearVal + val/7
+			} else {
+				yearVal = yearVal + val
+			}
+			yearTotalMap[currentDate.Year()] = yearVal
+			dateTotalMap[currentDate] = yearVal
+			dateIndexMap[currentDate] = i
+			indexDateMap[i] = currentDate
 		}
-		yearVal := yearTotalMap[currTime.Year()]
-		yearVal = yearVal + v.Value
-		yearTotalMap[currTime.Year()] = yearVal
-		dateTotalMap[currTime] = yearVal
-	}
-
-	//(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速);
-	for k, currentDate := range dayList {
-		currYearBalance := yearValueConfig - yearTotalMap[currentDate.Year()] // 当年的余额
-
-		// 上一期的日期
-		prevDateStr := allDataList[len(allDataList)-1].DataTime
-		prevDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, prevDateStr, time.Local)
-		if tmpErr != nil {
-			err = tmpErr
-			return
+	default:
+		for k, v := range allDataList {
+			currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			allDataListMap[v.DataTime] = v.Value
+			yearVal := yearTotalMap[currTime.Year()]
+			yearVal = yearVal + v.Value
+			yearTotalMap[currTime.Year()] = yearVal
+			dateTotalMap[currTime] = yearVal
+			dateIndexMap[currTime] = k
+			indexDateMap[k] = currTime
 		}
-
-		//同比年份相应日期
-		lastYear := annualValueInversionConf.Year + (currentDate.Year() - currDayTime.Year())
-
-		// 前N年的上一期时间;前N年的当期时间;
-		var lastPrevDateTime, lastDateTime time.Time
-
-		switch frequency {
-		case "半年度", "季度":
-			lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
-			lastPrevDateTime = time.Date(lastYear, prevDateTime.Month(), prevDateTime.Day(), 0, 0, 0, 0, prevDateTime.Location())
-		case "月度":
-			lastDateTime = time.Date(lastYear, currentDate.Month()+1, 1, 0, 0, 0, 0, currentDate.Location()).AddDate(0, 0, -1)
-			lastPrevDateTime = time.Date(lastYear, prevDateTime.Month()+1, 1, 0, 0, 0, 0, prevDateTime.Location()).AddDate(0, 0, -1)
-		case "旬度":
-			if prevDateTime.Day() == 10 || prevDateTime.Day() == 20 {
-				lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
-				lastPrevDateTime = time.Date(lastYear, prevDateTime.Month(), prevDateTime.Day(), 0, 0, 0, 0, prevDateTime.Location())
-			} else {
-				lastDateTime = time.Date(lastYear, currentDate.Month()+1, 1, 0, 0, 0, 0, currentDate.Location()).AddDate(0, 0, -1)
-				lastPrevDateTime = time.Date(lastYear, prevDateTime.Month()+1, 1, 0, 0, 0, 0, prevDateTime.Location()).AddDate(0, 0, -1)
+	}
+	// 当年的余额
+	currYearBalance := yearValueConfig - yearTotalMap[currDayTime.Year()]
+	//fmt.Printf("当年的余额%.4f=给定额度%.4f-当年累计值%.4f\n", currYearBalance, yearValueConfig, yearTotalMap[currDayTime.Year()])
+	// 循环统计同比年份同期余额
+	var sum, avg float64
+	for _, year := range yearList {
+		yearTotal := yearTotalMap[year]
+		//fmt.Printf("同比年份的累计值%.4f\n", yearTotal)
+		tmpDate := time.Date(year, currDayTime.Month(), currDayTime.Day(), 0, 0, 0, 0, currDayTime.Location())
+		//fmt.Printf("同比年份的同期%s\n", tmpDate)
+		dateTotal, ok := dateTotalMap[tmpDate]
+		//fmt.Printf("同比年份的同期累计值%.4f\n", dateTotal)
+		if ok {
+			sum = sum + (yearTotal - dateTotal)
+		} else {
+			// 查找下一期的余额
+			tmpIndex, ok1 := dateIndexMap[tmpDate]
+			if ok1 {
+				for tmpDateTime := indexDateMap[tmpIndex+1]; tmpDateTime.Year() == year; tmpDateTime = indexDateMap[tmpIndex+1] {
+					dateTotal, ok = dateTotalMap[tmpDateTime]
+					if ok {
+						//fmt.Printf("同比年份的同期累计值%.4f\n", dateTotal)
+						sum = sum + (yearTotal - dateTotal)
+						break
+					}
+					tmpIndex += 1
+				}
 			}
-		case "周度", "日度":
-			lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
-			lastPrevDateTime = time.Date(lastYear, prevDateTime.Month(), prevDateTime.Day(), 0, 0, 0, 0, prevDateTime.Location())
 		}
-
-		// 同比年份相应日期的累计值
-		var dateTotal float64
-
-		dateTotal, ok := dateTotalMap[lastPrevDateTime]
-		if !ok { //如果没有找到这个日期,那么就往前面找,一直到找到这个累计值,或者找完这一年
-			yearFirstDayTime := time.Date(lastPrevDateTime.Year(), 1, 1, 0, 0, 0, 0, lastDateTime.Location())
-			for tmpDateTime := lastPrevDateTime.AddDate(0, 0, -1); tmpDateTime.After(yearFirstDayTime) || tmpDateTime.Equal(yearFirstDayTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
-				dateTotal, ok = dateTotalMap[tmpDateTime]
-				if ok {
-					break
+	}
+	//fmt.Printf("同比年份的余额%.4f\n", sum)
+	avg = sum / float64(len(yearList))
+	//fmt.Printf("同比年份的余额%.4f\n", avg)
+	// 同比增速=当年余额/同比年份上一期日期的余额
+	tbVal := decimal.NewFromFloat(currYearBalance).Div(decimal.NewFromFloat(avg))
+	/*tbVal11, _ := tbVal.Round(4).Float64()
+	fmt.Printf("同比增速%.4f\n", tbVal11)*/
+	//(同比增速=余额/同比年份相应日期的余额的平均值,预测值等于同比年份同期值*同比增速);
+	for k, currentDate := range dayList {
+		// 循环遍历多个同比年份
+		var valSum float64
+		for _, year := range yearList {
+			//多个同比年份的同期值的平均值
+			tmpCurrentDate := time.Date(year, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
+			if tmpVal, ok := allDataListMap[tmpCurrentDate.Format(utils.FormatDate)]; ok {
+				valSum += tmpVal
+			} else {
+				// 查找下一期的余额
+				tmpIndex, ok1 := dateIndexMap[tmpCurrentDate]
+				if ok1 {
+					for tmpDateTime := indexDateMap[tmpIndex+1]; tmpDateTime.Year() == year; tmpDateTime = indexDateMap[tmpIndex+1] {
+						tmpVal, ok = allDataListMap[tmpDateTime.Format(utils.FormatDate)]
+						if ok {
+							valSum += tmpVal
+							break
+						}
+						tmpIndex += 1
+					}
 				}
 			}
 		}
+		lastDateVal := valSum / float64(len(yearList))
 
-		//同比年份相应的上一期日期的余额
-		lastYearDateBalance := yearTotalMap[lastPrevDateTime.Year()] - dateTotal
-		if lastYearDateBalance == 0 {
-			continue
+		//预测值 = 同比年份同期值*同比增速
+		tmpVal, _ := decimal.NewFromFloat(lastDateVal).Mul(tbVal).Round(4).Float64()
+		currentDateStr := currentDate.Format(utils.FormatDate)
+		tmpData := &data_manage.EdbDataList{
+			EdbDataId:     edbInfoId + 100000 + index + k,
+			EdbInfoId:     edbInfoId,
+			DataTime:      currentDateStr,
+			Value:         tmpVal,
+			DataTimestamp: currentDate.UnixNano() / 1e6,
 		}
+		newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
+		allDataList = append(allDataList, tmpData)
+		existMap[currentDateStr] = tmpVal
 
-		// 同比增速=当年余额/同比年份上一期日期的余额
-		tbVal := decimal.NewFromFloat(currYearBalance).Div(decimal.NewFromFloat(lastYearDateBalance))
-
-		// 获取同比年份同期值,获取失败的话,就不处理
-		if lastDateVal, ok := existMap[lastDateTime.Format(utils.FormatDate)]; ok {
-			//预测值 = 同比年份同期值*同比增速
-			tmpVal, _ := decimal.NewFromFloat(lastDateVal).Mul(tbVal).Round(4).Float64()
-			currentDateStr := currentDate.Format(utils.FormatDate)
-			tmpData := &data_manage.EdbDataList{
-				EdbDataId:     edbInfoId + 100000 + index + k,
-				EdbInfoId:     edbInfoId,
-				DataTime:      currentDateStr,
-				Value:         tmpVal,
-				DataTimestamp: currentDate.UnixNano() / 1e6,
-			}
-			newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
-			allDataList = append(allDataList, tmpData)
-			existMap[currentDateStr] = tmpVal
-
-			yearVal := yearTotalMap[currentDate.Year()]
-			yearVal = yearVal + tmpVal
-			yearTotalMap[currentDate.Year()] = yearVal
-			dateTotalMap[currentDate] = yearVal
+		yearVal := yearTotalMap[currentDate.Year()]
+		yearVal = yearVal + tmpVal
+		yearTotalMap[currentDate.Year()] = yearVal
+		dateTotalMap[currentDate] = yearVal
 
-			// 最大最小值
-			if tmpVal < minValue {
-				minValue = tmpVal
-			}
-			if tmpVal > maxValue {
-				maxValue = tmpVal
-			}
+		// 最大最小值
+		if tmpVal < minValue {
+			minValue = tmpVal
+		}
+		if tmpVal > maxValue {
+			maxValue = tmpVal
 		}
 	}
 

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

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

+ 555 - 0
services/document_manage_service/document_manage_service.go

@@ -0,0 +1,555 @@
+// @Author gmy 2024/9/19 16:45:00
+package document_manage_service
+
+import (
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/document_manage_model"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/beego/beego/v2/core/logs"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"strconv"
+)
+
+func DocumentClassifyList(userId int) ([]models.ClassifyVO, error) {
+	logs.Info("DocumentClassifyList")
+
+	//获取所有已启用分类
+	var condition string
+	pars := make([]interface{}, 0)
+
+	condition = ` and enabled=?`
+	pars = append(pars, 1)
+
+	classifyList, err := models.GetClassifyListByCondition(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	// 查询用户已收藏的分类
+	collectClassifyList, err := models.GetUserCollectClassifyList(userId, 0)
+	if err != nil {
+		return nil, err
+	}
+	// 转换成map
+	collectClassifyMap := make(map[int]bool)
+	for _, collectClassify := range collectClassifyList {
+		collectClassifyMap[collectClassify.ClassifyId] = true
+	}
+
+	// 递归处理分类 拿到父级分类和对应的子级分类
+	classifyVOList := make([]models.ClassifyVO, 0)
+	for _, classify := range classifyList {
+
+		if classify.ParentId == 0 {
+			classifyVO := models.ClassifyVO{
+				Id:           classify.Id,
+				ClassifyName: classify.ClassifyName,
+				ParentId:     classify.ParentId,
+				Sort:         classify.Sort,
+				Enabled:      classify.Enabled,
+				Level:        classify.Level,
+			}
+			if _, ok := collectClassifyMap[classify.Id]; ok {
+				classifyVO.IsCollect = 1
+			}
+
+			classifyVO.Children = getChildClassify(classifyList, classify.Id, collectClassifyMap)
+			classifyVOList = append(classifyVOList, classifyVO)
+		}
+	}
+
+	return classifyVOList, nil
+}
+
+func getChildClassify(classifyList []models.Classify, classifyId int, collectClassifyMap map[int]bool) *[]models.ClassifyVO {
+	childList := make([]models.ClassifyVO, 0)
+	for _, classify := range classifyList {
+		if classify.ParentId == classifyId {
+			classifyVO := models.ClassifyVO{
+				Id:           classify.Id,
+				ClassifyName: classify.ClassifyName,
+				ParentId:     classify.ParentId,
+				Sort:         classify.Sort,
+				Enabled:      classify.Enabled,
+			}
+			if _, ok := collectClassifyMap[classify.Id]; ok {
+				classifyVO.IsCollect = 1
+			}
+			classifyVO.Children = getChildClassify(classifyList, classify.Id, collectClassifyMap)
+			childList = append(childList, classifyVO)
+		}
+	}
+	return &childList
+}
+
+func DocumentCollectClassify(classifyId, userId int) error {
+	logs.Info("DocumentCollectClassify")
+
+	// 查询用户是否已收藏该分类
+	collectClassifyList, err := models.GetUserCollectClassifyList(userId, classifyId)
+	if err != nil {
+		return err
+	}
+	if len(collectClassifyList) > 0 {
+		err := models.DeleteUserCollectClassify(userId, classifyId)
+		if err != nil {
+			return err
+		}
+	} else {
+		err = models.InsertUserCollectClassify(models.UserCollectClassify{
+			ClassifyId: classifyId,
+			SysUserId:  userId,
+			CreateTime: utils.GetCurrentTime(),
+		})
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func DocumentVarietyList() ([]models.ChartPermissionVO, error) {
+	logs.Info("DocumentVarietyList")
+
+	cp := new(models.ChartPermission)
+	var condition string
+	pars := make([]interface{}, 0)
+
+	condition = ` and enabled=?`
+	pars = append(pars, 1)
+
+	chartPermissionList, err := cp.GetItemsByCondition(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	// 递归处理品种 拿到父级品种和对应的子级品种
+	permissionVOList := make([]models.ChartPermissionVO, 0)
+	for _, chartPermission := range chartPermissionList {
+		if chartPermission.ParentId == 0 {
+			permissionVO := models.ChartPermissionVO{
+				ChartPermissionId:   chartPermission.ChartPermissionId,
+				ChartPermissionName: chartPermission.ChartPermissionName,
+				PermissionName:      chartPermission.PermissionName,
+				ParentId:            chartPermission.ParentId,
+				Enabled:             chartPermission.Enabled,
+				Sort:                chartPermission.Sort,
+				CreatedTime:         chartPermission.CreatedTime,
+			}
+			permissionVO.Children = getChildVariety(chartPermissionList, permissionVO.ChartPermissionId)
+			permissionVOList = append(permissionVOList, permissionVO)
+		}
+	}
+
+	return permissionVOList, nil
+}
+
+func getChildVariety(permissionList []*models.ChartPermission, permissionId int) []*models.ChartPermissionVO {
+	childList := make([]*models.ChartPermissionVO, 0)
+	for _, permission := range permissionList {
+		if permission.ParentId == permissionId {
+			permissionVO := models.ChartPermissionVO{
+				ChartPermissionId:   permission.ChartPermissionId,
+				ChartPermissionName: permission.ChartPermissionName,
+				PermissionName:      permission.PermissionName,
+				ParentId:            permission.ParentId,
+				Enabled:             permission.Enabled,
+				Sort:                permission.Sort,
+				CreatedTime:         permission.CreatedTime,
+			}
+			permissionVO.Children = getChildVariety(permissionList, permissionVO.ChartPermissionId)
+			childList = append(childList, &permissionVO)
+		}
+	}
+	return childList
+
+}
+
+func DocumentReportList(userId, documentType int, chartPermissionIdList []string, classifyIdList []string, keyword string, orderField, orderType string, startSize, pageSize int) (*document_manage_model.OutsideReportPage, error) {
+	logs.Info("DocumentVarietyList")
+
+	var condition string
+	pars := make([]interface{}, 0)
+
+	if documentType == 1 {
+		condition = ` and t1.source!=3`
+	}
+	if len(classifyIdList) > 0 {
+		// 递归查询子分类
+		for _, classifyId := range classifyIdList {
+			id, err := strconv.Atoi(classifyId)
+			if err != nil {
+				return nil, err
+			}
+
+			if id == 0 {
+				classifyIdList = append(classifyIdList, classifyId)
+			} else {
+				childrenClassifyIdList, err := GetAllClassifyIdsByParentId(id)
+				if err != nil {
+					return nil, err
+				}
+				classifyIdList = append(classifyIdList, childrenClassifyIdList...)
+			}
+		}
+		condition += ` and t1.classify_id in (` + utils.GetOrmInReplace(len(classifyIdList)) + `)`
+		for _, classifyId := range classifyIdList {
+			pars = append(pars, classifyId)
+		}
+	}
+	if len(chartPermissionIdList) > 0 {
+		condition += ` and t2.chart_permission_id in (` + utils.GetOrmInReplace(len(chartPermissionIdList)) + `)`
+		for _, chartPermissionId := range chartPermissionIdList {
+			pars = append(pars, chartPermissionId)
+		}
+	}
+	if keyword != "" {
+		condition += ` and (t1.title like ? or t1.sys_user_name like ?) `
+		pars = append(pars, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	if len(chartPermissionIdList) > 0 {
+		condition += ` GROUP BY t1.outside_report_id HAVING COUNT(DISTINCT t2.chart_permission_id) = ` + strconv.Itoa(len(chartPermissionIdList))
+	}
+
+	count, err := document_manage_model.GetOutsideReportListByConditionCount(condition, pars, chartPermissionIdList)
+	if err != nil {
+		return nil, err
+	}
+	reportPage := document_manage_model.OutsideReportPage{}
+
+	page := paging.GetPaging(startSize, pageSize, count)
+	if count <= 0 {
+		reportPage.Paging = page
+		return &reportPage, nil
+	}
+
+	if orderField != "" && orderType != "" {
+		condition += ` order by t1.` + orderField + ` ` + orderType
+	} else {
+		condition += ` order by t1.report_update_time desc`
+	}
+
+	outsideReportList, err := document_manage_model.GetOutsideReportListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		return nil, err
+	}
+
+	// 查询用户已收藏的分类
+	collectClassifyList, err := models.GetUserCollectClassifyList(userId, 0)
+	if err != nil {
+		return nil, err
+	}
+	// 转换成map
+	collectClassifyMap := make(map[int]bool)
+	for _, collectClassify := range collectClassifyList {
+		collectClassifyMap[collectClassify.ClassifyId] = true
+	}
+
+	for i, _ := range outsideReportList {
+		if _, ok := collectClassifyMap[outsideReportList[i].ClassifyId]; ok {
+			outsideReportList[i].IsCollect = 1
+		}
+	}
+
+	reportPage.Paging = page
+	reportPage.List = outsideReportList
+
+	return &reportPage, nil
+}
+
+// GetAllClassifyIdsByParentId 递归查询子分类
+func GetAllClassifyIdsByParentId(parentId int) ([]string, error) {
+	var classifyIdList []string
+
+	// 获取子分类
+	classifyList, err := models.GetClassifyListByParentId(parentId)
+	if err != nil {
+		return nil, err
+	}
+
+	// 遍历子分类
+	for _, classify := range classifyList {
+		classifyIdList = append(classifyIdList, strconv.Itoa(classify.Id))
+		// 递归调用
+		subClassifyIds, err := GetAllClassifyIdsByParentId(classify.Id)
+		if err != nil {
+			return nil, err
+		}
+		classifyIdList = append(classifyIdList, subClassifyIds...)
+	}
+
+	return classifyIdList, nil
+}
+
+func RuiSiReportList(classifyIdFirst, classifyIdSecond, classifyIdThird []string, keyword, orderField, orderType string, startSize, pageSize int) (*models.ReportListResp, error) {
+	logs.Info("RuiSiReportList")
+
+	// 作者为 全球市场战略研究中心 PCI Research
+	author := "全球市场战略研究中心 PCI Research"
+	// 已发布的报告
+	state := 2
+
+	count, err := models.GetReportListByCollectCount(classifyIdFirst, classifyIdSecond, classifyIdThird, keyword, author, state)
+	if err != nil {
+		return nil, err
+	}
+	reportPage := models.ReportListResp{}
+
+	page := paging.GetPaging(startSize, pageSize, count)
+	if count <= 0 {
+		reportPage.Paging = page
+		return &reportPage, nil
+	}
+
+	reportList, err := models.GetReportListByCollectList(classifyIdFirst, classifyIdSecond, classifyIdThird, keyword, orderField, orderType, startSize, pageSize, author, state)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, report := range reportList {
+		report.ModifyTime = report.ModifyTime.UTC()
+	}
+
+	reportPage.Paging = page
+	reportPage.List = reportList
+
+	return &reportPage, nil
+}
+
+func DocumentRuiSiDetail(reportId int) (*models.ReportDetail, error) {
+	logs.Info("DocumentRuiSiDetail")
+
+	reportDetail, err := models.GetReportById(reportId)
+	if err != nil {
+		return nil, err
+	}
+
+	return reportDetail, nil
+}
+
+func DocumentSave(outsideReport *document_manage_model.OutsideReportBO) error {
+	logs.Info("DocumentSave")
+
+	// 保存报告
+	report := document_manage_model.OutsideReport{
+		Source:           outsideReport.Source,
+		Title:            outsideReport.Title,
+		Abstract:         outsideReport.Abstract,
+		ClassifyId:       outsideReport.ClassifyId,
+		ClassifyName:     outsideReport.ClassifyName,
+		Content:          outsideReport.Content,
+		SysUserId:        outsideReport.SysUserId,
+		SysUserName:      outsideReport.SysUserName,
+		ReportUpdateTime: utils.GetCurrentTime(),
+		ModifyTime:       utils.GetCurrentTime(),
+		CreateTime:       utils.GetCurrentTime(),
+	}
+	id, err := document_manage_model.SaveOutsideReport(report)
+	if err != nil {
+		return err
+	}
+
+	// 保存附件
+	attachmentList := outsideReport.AttachmentList
+	if len(attachmentList) > 0 {
+		for _, attachment := range attachmentList {
+			if attachment.Title == "" || attachment.Url == "" {
+				continue
+			}
+			attachment.OutsideReportId = int(id)
+			attachment.CreateTime = utils.GetCurrentTime()
+			_, err := document_manage_model.SaveOutsideReportAttachment(attachment)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return err
+}
+
+func DocumentReportDetail(outsideReportId int) (*document_manage_model.OutsideReportBO, error) {
+	logs.Info("DocumentReportDetail")
+
+	outsideReport, err := document_manage_model.GetOutsideReportById(outsideReportId)
+	if err != nil {
+		return nil, err
+	}
+
+	attachmentList, err := document_manage_model.GetOutsideReportAttachmentListByReportId(outsideReportId)
+	if err != nil {
+		return nil, err
+	}
+
+	outsideReportBO := document_manage_model.OutsideReportBO{
+		OutsideReportId: outsideReportId,
+		Source:          outsideReport.Source,
+		Title:           outsideReport.Title,
+		Abstract:        outsideReport.Abstract,
+		ClassifyId:      outsideReport.ClassifyId,
+		ClassifyName:    outsideReport.ClassifyName,
+		Content:         html.UnescapeString(outsideReport.Content),
+		SysUserId:       outsideReport.SysUserId,
+		SysUserName:     outsideReport.SysUserName,
+		CreateTime:      outsideReport.CreateTime,
+		ModifyTime:      outsideReport.ModifyTime,
+		ReportCode:      outsideReport.ReportCode,
+		AttachmentList:  attachmentList,
+	}
+	return &outsideReportBO, nil
+}
+
+func DocumentUpdate(outsideReport *document_manage_model.OutsideReportBO) error {
+	logs.Info("DocumentUpdate")
+	report, err := document_manage_model.GetOutsideReportById(outsideReport.OutsideReportId)
+	if err != nil {
+		return err
+	}
+
+	if report == nil {
+		return fmt.Errorf("报告不存在")
+	}
+	// 更新报告
+	if outsideReport.Title != "" {
+		report.Title = outsideReport.Title
+	}
+	if outsideReport.Abstract != "" {
+		report.Abstract = outsideReport.Abstract
+	}
+	if outsideReport.ClassifyId > 0 {
+		report.ClassifyId = outsideReport.ClassifyId
+	}
+	if outsideReport.ClassifyName != "" {
+		report.ClassifyName = outsideReport.ClassifyName
+	}
+	if outsideReport.Content != "" {
+		report.Content = outsideReport.Content
+	}
+	report.ModifyTime = utils.GetCurrentTime()
+	report.ReportUpdateTime = utils.GetCurrentTime()
+	err = document_manage_model.UpdateOutsideReport(report)
+	if err != nil {
+		return fmt.Errorf("更新报告失败, Err: %s", err.Error())
+	}
+
+	// 更新报告附件
+	attachmentList := outsideReport.AttachmentList
+	if len(attachmentList) > 0 {
+		err = document_manage_model.DeleteReportAttachmentByReportId(outsideReport.OutsideReportId)
+		if err != nil {
+			return fmt.Errorf("删除报告附件失败, Err: %s", err.Error())
+		}
+
+		for _, attachment := range attachmentList {
+			if attachment.Title == "" || attachment.Url == "" {
+				continue
+			}
+			attachment.OutsideReportId = outsideReport.OutsideReportId
+			attachment.CreateTime = utils.GetCurrentTime()
+			_, err := document_manage_model.SaveOutsideReportAttachment(attachment)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func DocumentDelete(outsideReportId int) error {
+	logs.Info("DocumentDelete")
+
+	report, err := document_manage_model.GetOutsideReportById(outsideReportId)
+	if err != nil {
+		return err
+	}
+	if report == nil {
+		return fmt.Errorf("报告不存在")
+	}
+
+	err = document_manage_model.DeleteOutsideReport(outsideReportId)
+	if err != nil {
+		return err
+	}
+	err = document_manage_model.DeleteReportAttachmentByReportId(outsideReportId)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func RuiSiReportListV2(classifyIdList, chartPermissionIdList []string, keyword, orderField, orderType string, userId, startSize, pageSize int) (*models.ReportListResp, error) {
+	logs.Info("RuiSiReportList")
+
+	allClassifyList, err := models.GetClassifyListByIdStrList(classifyIdList)
+	if err != nil {
+		return nil, err
+	}
+	classifyIdFirst := make([]string, 0)
+	classifyIdSecond := make([]string, 0)
+	classifyIdThird := make([]string, 0)
+	for _, v := range allClassifyList {
+		switch v.Level {
+		case 1:
+			classifyIdFirst = append(classifyIdFirst, strconv.Itoa(v.Id))
+		case 2:
+			classifyIdSecond = append(classifyIdSecond, strconv.Itoa(v.Id))
+		case 3:
+			classifyIdThird = append(classifyIdThird, strconv.Itoa(v.Id))
+		}
+	}
+	// 作者为 全球市场战略研究中心 PCI Research
+	var author string
+	//author := "战研中心 PCIR"
+	// 已发布/已审批的公开发布的报告
+	isPublic := 1
+	stateArr := []int{2, 6}
+
+	count, err := models.GetReportListByCollectCountV2(classifyIdFirst, classifyIdSecond, classifyIdThird, chartPermissionIdList, keyword, author, isPublic, stateArr)
+	if err != nil {
+		return nil, err
+	}
+	reportPage := models.ReportListResp{}
+
+	page := paging.GetPaging(startSize, pageSize, count)
+	if count <= 0 {
+		reportPage.Paging = page
+		return &reportPage, nil
+	}
+
+	reportList, err := models.GetReportListByCollectListV2(classifyIdFirst, classifyIdSecond, classifyIdThird, chartPermissionIdList, keyword, orderField, orderType, startSize, pageSize, author, isPublic, stateArr)
+	if err != nil {
+		return nil, err
+	}
+
+	// 查询用户已收藏的分类
+	collectClassifyList, err := models.GetUserCollectClassifyList(userId, 0)
+	if err != nil {
+		return nil, err
+	}
+	// 转换成map
+	collectClassifyMap := make(map[int]bool)
+	for _, collectClassify := range collectClassifyList {
+		collectClassifyMap[collectClassify.ClassifyId] = true
+	}
+
+	for i, report := range reportList {
+		report.ModifyTime = report.ModifyTime.UTC()
+		if _, ok := collectClassifyMap[reportList[i].ClassifyIdFirst]; ok {
+			reportList[i].IsCollect = 1
+		}
+		if _, ok := collectClassifyMap[reportList[i].ClassifyIdSecond]; ok {
+			reportList[i].IsCollect = 1
+		}
+		if _, ok := collectClassifyMap[reportList[i].ClassifyIdThird]; ok {
+			reportList[i].IsCollect = 1
+		}
+	}
+
+	reportPage.Paging = page
+	reportPage.List = reportList
+
+	return &reportPage, nil
+}

+ 198 - 8
services/elastic/elastic.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/models/data_manage/excel"
 	"eta/eta_mobile/utils"
 	"fmt"
 	"github.com/olivere/elastic/v7"
@@ -827,6 +828,14 @@ func searchEdbInfoData(indexName string, mustMap, mustNotMap []interface{}, shou
 
 	queryMap["from"] = from
 	queryMap["size"] = size
+	queryMap["highlight"] = map[string]interface{}{
+		"fields": map[string]interface{}{
+			"EdbName": map[string]interface{}{},
+		},
+		"pre_tags":  "<span style=\"color:#0052D9\">",
+		"post_tags": "</span>",
+	}
+
 	jsonBytes, _ := json.Marshal(queryMap)
 	fmt.Println(string(jsonBytes))
 
@@ -884,7 +893,11 @@ func searchEdbInfoData(indexName string, mustMap, mustNotMap []interface{}, shou
 					edbInfoItem.EdbCode = v.Highlight["EdbCode"][0]
 				}
 				if len(v.Highlight["EdbName"]) > 0 {
-					edbInfoItem.EdbCode = v.Highlight["EdbName"][0]
+					// 搜索结果高亮用新字段,原EdbName直接高亮展示上会有点影响
+					edbInfoItem.SearchText = v.Highlight["EdbName"][0]
+					//edbInfoItem.EdbName = v.Highlight["EdbName"][0]
+				} else {
+					edbInfoItem.SearchText = edbInfoItem.EdbName
 				}
 				list = append(list, edbInfoItem)
 				searchMap[v.Id] = v.Id
@@ -1224,8 +1237,8 @@ func EsDeleteDataV2(indexName, docId string) (err error) {
 }
 
 // SearchChartInfoData 查询es中的图表数据
-func SearchChartInfoData(indexName, keywordStr string, showSysId int, sourceList []int, noPermissionChartIdList []int, from, size int) (list []*data_manage.ChartInfo, total int64, err error) {
-	list = make([]*data_manage.ChartInfo, 0)
+func SearchChartInfoData(indexName, keywordStr string, showSysId int, sourceList []int, noPermissionChartIdList []int, from, size int) (list []*data_manage.ChartInfoMore, total int64, err error) {
+	list = make([]*data_manage.ChartInfoMore, 0)
 	defer func() {
 		if err != nil {
 			fmt.Println("EsAddOrEditData Err:", err.Error())
@@ -1362,6 +1375,14 @@ func SearchChartInfoData(indexName, keywordStr string, showSysId int, sourceList
 	// 分页查询
 	queryMap["from"] = from
 	queryMap["size"] = size
+	queryMap["highlight"] = map[string]interface{}{
+		"fields": map[string]interface{}{
+			keywordNameKey: map[string]interface{}{},
+		},
+		"pre_tags":  "<span style=\"color:#0052D9\">",
+		"post_tags": "</span>",
+	}
+
 	jsonBytes, _ := json.Marshal(queryMap)
 	fmt.Println(string(jsonBytes))
 
@@ -1393,15 +1414,18 @@ func SearchChartInfoData(indexName, keywordStr string, showSysId int, sourceList
 					fmt.Println("movieJson err:", err)
 					return
 				}
-				chartInfoItem := new(data_manage.ChartInfo)
+				chartInfoItem := new(data_manage.ChartInfoMore)
 				tmpErr = json.Unmarshal(itemJson, &chartInfoItem)
 				if err != nil {
 					fmt.Println("json.Unmarshal chartInfoJson err:", err)
 					err = tmpErr
 					return
 				}
-				if len(v.Highlight["ChartName"]) > 0 {
-					chartInfoItem.ChartName = v.Highlight["ChartName"][0]
+				if len(v.Highlight[keywordNameKey]) > 0 {
+					//chartInfoItem.ChartName = v.Highlight["ChartName"][0]
+					chartInfoItem.SearchText = v.Highlight[keywordNameKey][0]
+				} else {
+					chartInfoItem.SearchText = chartInfoItem.ChartName
 				}
 				list = append(list, chartInfoItem)
 				searchMap[v.Id] = v.Id
@@ -1574,6 +1598,14 @@ func SearchMyChartInfoData(indexName, keywordStr string, adminId int, noPermissi
 	// 分页查询
 	queryMap["from"] = from
 	queryMap["size"] = size
+	queryMap["highlight"] = map[string]interface{}{
+		"fields": map[string]interface{}{
+			keywordNameKey: map[string]interface{}{},
+		},
+		"pre_tags":  "<span style=\"color:#0052D9\">",
+		"post_tags": "</span>",
+	}
+
 	jsonBytes, _ := json.Marshal(queryMap)
 	fmt.Println(string(jsonBytes))
 
@@ -1612,8 +1644,11 @@ func SearchMyChartInfoData(indexName, keywordStr string, adminId int, noPermissi
 					err = tmpErr
 					return
 				}
-				if len(v.Highlight["ChartName"]) > 0 {
-					chartInfoItem.ChartName = v.Highlight["ChartName"][0]
+				if len(v.Highlight[keywordNameKey]) > 0 {
+					//chartInfoItem.ChartName = v.Highlight["ChartName"][0]
+					chartInfoItem.SearchText = v.Highlight[keywordNameKey][0]
+				} else {
+					chartInfoItem.SearchText = chartInfoItem.ChartName
 				}
 				list = append(list, chartInfoItem)
 				searchMap[v.Id] = v.Id
@@ -1850,3 +1885,158 @@ func SearchEdbInfoDataByAdminId(indexName, keywordStr string, from, size, filter
 
 	return searchEdbInfoData(indexName, mustMap, mustNotMap, shouldMap, from, size)
 }
+
+// SearchExcelInfoData 查询es中的表格
+func SearchExcelInfoData(indexName, keyword string, source, adminId int, queryIds, exceptIds []int, from, size int) (total int64, list []*excel.SearchExcelInfo, err error) {
+	list = make([]*excel.SearchExcelInfo, 0)
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("SearchExcelInfoData err: %v", err)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	client := utils.EsClient
+
+	mustMap := make([]interface{}, 0)
+	mustNotMap := make([]interface{}, 0)
+	mustMap = append(mustMap, map[string]interface{}{
+		"term": map[string]interface{}{
+			"IsDelete": 0,
+		},
+	})
+
+	// 表格名称
+	shouldMap := make(map[string]interface{}, 0)
+	if keyword != "" {
+		shouldMap["should"] = []interface{}{
+			map[string]interface{}{
+				"match": map[string]interface{}{
+					"ExcelName": keyword,
+				},
+			},
+		}
+
+		//shouldMap := map[string]interface{}{
+		//	"should": []interface{}{
+		//		map[string]interface{}{
+		//			"match": map[string]interface{}{
+		//				"ExcelName": keyword,
+		//			},
+		//		},
+		//	},
+		//}
+	}
+
+	// 表格来源
+	if source > 0 {
+		mustMap = append(mustMap, map[string]interface{}{
+			"term": map[string]interface{}{
+				"Source": source,
+			},
+		})
+	}
+
+	// 创建人
+	if adminId > 0 {
+		mustMap = append(mustMap, map[string]interface{}{
+			"term": map[string]interface{}{
+				"SysUserId": adminId,
+			},
+		})
+	}
+
+	// 查询和排除的表格IDs
+	if len(queryIds) > 0 {
+		mustMap = append(mustMap, map[string]interface{}{
+			"terms": map[string]interface{}{
+				"ExcelInfoId": queryIds,
+			},
+		})
+	}
+	if len(exceptIds) > 0 {
+		mustNotMap = append(mustNotMap, map[string]interface{}{
+			"terms": map[string]interface{}{
+				"ExcelInfoId": exceptIds,
+			},
+		})
+	}
+
+	// 关键字匹配
+	mustMap = append(mustMap, map[string]interface{}{
+		"bool": shouldMap,
+	})
+	queryMap := map[string]interface{}{
+		"query": map[string]interface{}{
+			"bool": map[string]interface{}{
+				"must":     mustMap,
+				"must_not": mustNotMap,
+				//"should":   shouldMap,
+			},
+		},
+	}
+
+	//jsonBytes, _ := json.Marshal(queryMap)
+	//fmt.Println(string(jsonBytes))
+
+	// 根据条件数量统计
+	requestTotalHits := client.Count(indexName).BodyJson(queryMap)
+	t, e := requestTotalHits.Do(context.Background())
+	if e != nil {
+		err = fmt.Errorf("total hits err: %v", e)
+		return
+	}
+	total = t
+
+	// 表格名称高亮,分页
+	highlightKeyName := "ExcelName"
+	queryMap["highlight"] = map[string]interface{}{
+		"fields": map[string]interface{}{
+			highlightKeyName: map[string]interface{}{},
+		},
+		"pre_tags":  "<span style=\"color:#0052D9\">",
+		"post_tags": "</span>",
+	}
+	queryMap["from"] = from
+	queryMap["size"] = size
+
+	//jsonBytes, _ := json.Marshal(queryMap)
+	//fmt.Println(string(jsonBytes))
+
+	request := client.Search(indexName).Source(queryMap)
+	searchResp, e := request.Do(context.Background())
+	if e != nil {
+		err = fmt.Errorf("search do err: %v", e)
+		return
+	}
+	//fmt.Println(searchResp)
+	if searchResp.Status != 0 {
+		return
+	}
+	if searchResp.Hits == nil {
+		return
+	}
+	searchMap := make(map[string]string)
+	for _, v := range searchResp.Hits.Hits {
+		if _, ok := searchMap[v.Id]; ok {
+			continue
+		}
+		j, e := v.Source.MarshalJSON()
+		if e != nil {
+			err = fmt.Errorf("hits json err: %v", e)
+			return
+		}
+		item := new(excel.SearchExcelInfo)
+		if e = json.Unmarshal(j, &item); e != nil {
+			err = fmt.Errorf("hits json unmarshal err: %v", e)
+			return
+		}
+		if len(v.Highlight[highlightKeyName]) > 0 {
+			item.SearchText = v.Highlight[highlightKeyName][0]
+		} else {
+			item.SearchText = item.ExcelName
+		}
+		list = append(list, item)
+		searchMap[v.Id] = v.Id
+	}
+	return
+}

+ 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
+}

+ 217 - 0
services/eta_forum/chart_collect.go

@@ -0,0 +1,217 @@
+package eta_forum
+
+import (
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/services/alarm_msg"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type UserChartListReq struct {
+	Keyword         string `description:"搜索关键词"`
+	PageSize        int
+	CurrentIndex    int
+	UserMobile      string `description:"用户手机号"`
+	BusinessCode    string `description:"商户号"`
+	UserTelAreaCode string `description:"手机号区号"`
+}
+
+type UserChartListResp struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        *UserChartListRespItem
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+type UserChartListRespItem struct {
+	ChartInfoList []*data_manage.ChartInfo
+	Paging        *paging.PagingItem `description:"分页数据"`
+}
+
+type UserCollectChartClassifyListResp struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        *UserCollectChartClassifyListItem
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+type UserCollectChartClassifyListItem struct {
+	List     []*ChartCollectClassifyItem
+	Language string `description:"指标的展示语言,CN:中文,EN:英文"`
+}
+
+// ChartCollectClassifyItem 我的图表分类信息
+type ChartCollectClassifyItem struct {
+	CollectClassifyId int    `description:"分类ID"`
+	ClassifyName      string `description:"分类名称"`
+	UserId            int    `description:"创建人id"`
+	ChartNum          int    `description:"分类下的图表数量"`
+}
+
+type UserCollectChartListResp struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        *UserCollectChartListRespItem
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+type UserCollectChartListRespItem struct {
+	Paging *paging.PagingItem
+	List   []*ChartCollectView
+}
+
+type ChartCollectView struct {
+	ChartCollectId    int
+	ChartInfoId       int       `description:"图表id"`
+	UserId            int       `description:"用户id"`
+	CreateTime        time.Time `description:"创建时间"`
+	CollectTime       time.Time `description:"收藏时间"`
+	CollectClassifyId int
+	ChartName         string `description:"来源名称"`
+	ChartNameEn       string `description:"英文图表名称"`
+	ChartImage        string `description:"图表图片"`
+	UniqueCode        string `description:"图表唯一编码"`
+}
+
+type ChartFromUniqueCodeResp struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        *ChartFromUniqueCodeRespItem
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+type ChartFromUniqueCodeRespItem struct {
+	ChartInfo   *data_manage.ChartInfoView
+	EdbInfoList []*data_manage.ChartEdbInfoMapping
+	XEdbIdValue []int               `description:"柱方图的x轴数据,指标id"`
+	YDataList   []data_manage.YData `description:"柱方图的y轴数据"`
+	XDataList   []data_manage.XData `description:"商品价格曲线的X轴数据"`
+	//BarChartInfo BarChartInfoReq `description:"柱方图的配置"`
+	//CorrelationChartInfo *CorrelationInfo `description:"相关性图表信息"`
+	DataResp  interface{} `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
+	WaterMark string      `description:"水印"`
+}
+
+// GetUserChartList 查询社区中对用户可见的图表列表
+func GetUserChartList(businessCode, userMobile, telAreaCode, keyword string, currentIndex, pageSize int) (resp UserChartListRespItem, err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprintf("查询社区中对用户可见的图表列表 GetUserChartList:Err:%v,ErrMsg:%s", err, errMsg)
+			utils.FileLog.Info(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+	urlQuery := fmt.Sprintf("BusinessCode=%s&UserMobile=%s&UserTelAreaCode=%s&Keyword=%s&CurrentIndex=%d&PageSize=%d", businessCode, userMobile, telAreaCode, keyword, currentIndex, pageSize)
+	result, err := GetUserChartListLib(urlQuery)
+	if err != nil {
+		errMsg = "查询失败"
+		err = fmt.Errorf("查询失败,Err:" + err.Error())
+		return
+	}
+	if result.Ret != 200 {
+		errMsg = "查询失败"
+		err = fmt.Errorf(result.Msg + result.ErrMsg)
+		return
+	}
+	resp.Paging = result.Data.Paging
+	resp.ChartInfoList = result.Data.ChartInfoList
+
+	return
+}
+
+// GetUserCollectChartClassifyList 查询社区中用户收藏的分类列表
+func GetUserCollectChartClassifyList(businessCode, userMobile, telAreaCode string) (resp UserCollectChartClassifyListItem, err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprintf("查询社区中用户收藏的分类列表 GetUserCollectChartClassifyList:Err:%v,ErrMsg:%s", err, errMsg)
+			utils.FileLog.Info(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+	urlQuery := fmt.Sprintf("BusinessCode=%s&UserMobile=%s&UserTelAreaCode=%s", businessCode, userMobile, telAreaCode)
+	result, err := getUserCollectChartClassifyLib(urlQuery)
+	if err != nil {
+		errMsg = "查询失败"
+		err = fmt.Errorf("查询失败,Err:" + err.Error())
+		return
+	}
+	if result.Ret != 200 {
+		errMsg = "查询失败"
+		err = fmt.Errorf(result.Msg + result.ErrMsg)
+		return
+	}
+	resp.List = result.Data.List
+	resp.Language = result.Data.Language
+
+	return
+}
+
+// GetUserCollectChartList 查询社区中对用户收藏的图表列表
+func GetUserCollectChartList(businessCode, userMobile, telAreaCode, keyword, collectClassifyIds string, currentIndex, pageSize int) (resp UserCollectChartListRespItem, err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprintf("查询社区中对用户收藏的图表列表 GetUserCollectChartList:Err:%v,ErrMsg:%s", err, errMsg)
+			utils.FileLog.Info(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+	urlQuery := fmt.Sprintf("BusinessCode=%s&UserMobile=%s&UserTelAreaCode=%s&Keyword=%s&CollectClassifyIds=%s&CurrentIndex=%d&PageSize=%d", businessCode, userMobile, telAreaCode, keyword, collectClassifyIds, currentIndex, pageSize)
+	result, err := getUserCollectChartListLib(urlQuery)
+	if err != nil {
+		errMsg = "查询失败"
+		err = fmt.Errorf("查询失败,Err:" + err.Error())
+		return
+	}
+	if result.Ret != 200 {
+		errMsg = "查询失败"
+		err = fmt.Errorf(result.Msg + result.ErrMsg)
+		return
+	}
+	resp.Paging = result.Data.Paging
+	resp.List = result.Data.List
+
+	return
+}
+
+// GeChartFromUniqueCode 社区中根据唯一编码查询图表
+func GeChartFromUniqueCode(uniqueCode string, isCache bool) (resp ChartFromUniqueCodeRespItem, err error, errMsg string) {
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprintf("查询社区中对用户可见的图表列表 GeChartFromUniqueCode:Err:%v,ErrMsg:%s", err, errMsg)
+			utils.FileLog.Info(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+	}()
+	urlQuery := fmt.Sprintf("UniqueCode=%s&isCache=%v", uniqueCode, isCache)
+	result, err := getChartFromUniqueCodeLib(urlQuery)
+	if err != nil {
+		errMsg = "查询失败"
+		err = fmt.Errorf("查询失败,Err:" + err.Error())
+		return
+	}
+	if result.Ret != 200 {
+		errMsg = "查询失败"
+		err = fmt.Errorf(result.Msg + result.ErrMsg)
+		return
+	}
+	resp = *result.Data
+
+	return
+}

+ 88 - 0
services/eta_forum/eta_forum_hub_lib.go

@@ -0,0 +1,88 @@
+package eta_forum
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+)
+
+// GetUserChartListLib 查询有权限的图表列表
+func GetUserChartListLib(req string) (resp *UserChartListResp, err error) {
+	_, resultByte, err := get(req, "/v1/chart/user/chart_list")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// getUserCollectChartClassifyLib 查询收藏的分类列表
+func getUserCollectChartClassifyLib(req string) (resp *UserCollectChartClassifyListResp, err error) {
+	_, resultByte, err := get(req, "/v1/chart_collect/classify/list")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// getUserCollectChartListLib 查询收藏的图表列表
+func getUserCollectChartListLib(req string) (resp *UserCollectChartListResp, err error) {
+	_, resultByte, err := get(req, "/v1/chart_collect/chart/list")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// getChartFromUniqueCodeLib 根据唯一编码查询图表
+func getChartFromUniqueCodeLib(req string) (resp ChartFromUniqueCodeResp, err error) {
+	_, resultByte, err := get(req, "/v1/chart/common/from_unique_code")
+	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 == "" {
+		err = fmt.Errorf("ETA社区桥接服务地址为空")
+		return
+	}
+	urlStr = urlStr + "?" + paramStr
+	getUrl := utils.ETA_FORUM_HUB_URL + urlStr
+	result, err = HttpGet(getUrl)
+	if err != nil {
+		err = fmt.Errorf("调用ETA社区桥接服务接口失败 error:%s", err.Error())
+		return
+	}
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func HttpGet(url string) ([]byte, error) {
+	client := &http.Client{}
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	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
+}

+ 220 - 0
services/excel/lucky_sheet.go

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

+ 66 - 0
services/excel_info.go

@@ -3,7 +3,9 @@ package services
 import (
 	"encoding/json"
 	"eta/eta_mobile/models"
+	excel3 "eta/eta_mobile/models/data_manage/excel"
 	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/services/data/data_manage_permission"
 	"eta/eta_mobile/utils"
 	"fmt"
 	"time"
@@ -60,3 +62,67 @@ func UpdateExcelEditMark(excelInfoId, nowUserId, status int, nowUserName string)
 	}
 	return
 }
+
+// GetBalanceExcelIdsByAdminId 获取用户有权限的平衡表excelIds
+func GetBalanceExcelIdsByAdminId(adminId int, condition string, pars []interface{}, permissionEdbIdList, permissionClassifyIdList []int) (authIds []int, err error) {
+	//找到当前协作人相关的表格ID
+	obj := new(excel3.ExcelWorker)
+	existList, err := obj.GetBySysUserId(adminId)
+	if err != nil {
+		//br.Msg = "获取表格协作人失败!"
+		//br.ErrMsg = "获取表格协作人失败,Err:" + err.Error()
+		return
+	}
+	var excelIds []int
+	newCondition := condition
+	newPars := pars
+	if len(existList) > 0 {
+		for _, v := range existList {
+			excelIds = append(excelIds, v.ExcelInfoId)
+		}
+		newCondition += fmt.Sprintf(` AND  ( excel_info_id IN (%s)  or sys_user_id = ?)`, utils.GetOrmInReplace(len(excelIds)))
+		newPars = append(newPars, excelIds, adminId)
+	} else {
+		newCondition += ` AND  sys_user_id = ? `
+		newPars = append(newPars, adminId)
+	}
+
+	//获取表格信息
+	tmpList, e := excel3.GetNoContentExcelListByConditionNoPage(newCondition, newPars)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		//br.Success = true
+		//br.Msg = "获取表格信息失败"
+		//br.ErrMsg = "获取表格信息失败,Err:" + e.Error()
+		return
+	}
+	classifyIdListTmp := make([]int, 0)
+	for _, v := range tmpList {
+		classifyIdListTmp = append(classifyIdListTmp, v.ExcelClassifyId)
+	}
+	classifyMap := make(map[int]*excel3.ExcelClassify)
+
+	// 分类信息
+	if len(classifyIdListTmp) > 0 {
+		classifyListTmp, e := excel3.GetClassifyByIdList(classifyIdListTmp)
+		if e != nil {
+			//br.Msg = "获取表格分类信息失败"
+			//br.ErrMsg = "获取表格分类列表数据失败,Err:" + e.Error()
+			return
+		}
+		for _, v := range classifyListTmp {
+			classifyMap[v.ExcelClassifyId] = v
+		}
+	}
+	excelIds = make([]int, 0)
+	for _, v := range tmpList {
+		// 数据权限
+		if classifyInfo, ok := classifyMap[v.ExcelClassifyId]; ok {
+			v.HaveOperaAuth = data_manage_permission.CheckExcelPermissionByPermissionIdList(v.IsJoinPermission, classifyInfo.IsJoinPermission, v.ExcelInfoId, v.ExcelClassifyId, permissionEdbIdList, permissionClassifyIdList)
+			if v.HaveOperaAuth {
+				excelIds = append(excelIds, v.ExcelInfoId)
+			}
+		}
+	}
+	authIds = excelIds
+	return
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff