Browse Source

Merge branch 'master' into feature/change_log

xyxie 1 year ago
parent
commit
6e908086c5
75 changed files with 11985 additions and 1478 deletions
  1. 2 1
      .gitignore
  2. 728 0
      controllers/data_manage/chart_framework.go
  3. 1 0
      controllers/data_manage/edb_classify.go
  4. 71 3
      controllers/data_manage/edb_info.go
  5. 591 0
      controllers/data_manage/excel/custom_analysis.go
  6. 469 0
      controllers/data_manage/excel/custom_analysis_edb.go
  7. 74 67
      controllers/data_manage/excel/excel_classify.go
  8. 334 211
      controllers/data_manage/excel/excel_info.go
  9. 172 0
      controllers/data_manage/jiayue_edb_source.go
  10. 75 0
      controllers/data_manage/my_chart.go
  11. 1 0
      controllers/data_manage/predict_edb_classify.go
  12. 63 3
      controllers/report.go
  13. 1851 293
      controllers/sandbox/sandbox.go
  14. 1238 0
      controllers/smart_report/smart_report.go
  15. 20 0
      controllers/sys_admin.go
  16. 14 0
      controllers/sys_role.go
  17. 11 5
      controllers/voice.go
  18. 0 0
      etalogs/binlog/20231023.log
  19. 4 2
      go.mod
  20. 31 8
      go.sum
  21. 355 0
      models/data_manage/chart_framework.go
  22. 135 0
      models/data_manage/chart_framework_node.go
  23. 3 3
      models/data_manage/edb_classify.go
  24. 5 2
      models/data_manage/edb_data_base.go
  25. 1 0
      models/data_manage/edb_info.go
  26. 3 1
      models/data_manage/edb_info_calculate.go
  27. 37 5
      models/data_manage/edb_source.go
  28. 13 7
      models/data_manage/excel/excel_classify.go
  29. 1 1
      models/data_manage/excel/excel_draft.go
  30. 112 0
      models/data_manage/excel/excel_edb_mapping.go
  31. 247 28
      models/data_manage/excel/excel_info.go
  32. 98 0
      models/data_manage/excel/excel_sheet.go
  33. 63 0
      models/data_manage/excel/excel_sheet_data.go
  34. 45 0
      models/data_manage/excel/request/excel.go
  35. 1 0
      models/data_manage/excel/request/excel_classify.go
  36. 17 0
      models/data_manage/excel/request/excel_info.go
  37. 4 2
      models/data_manage/excel/response/excel_classify.go
  38. 14 10
      models/data_manage/excel/response/excel_info.go
  39. 33 0
      models/data_manage/excel/response/sheet.go
  40. 147 0
      models/data_manage/jiayue_index.go
  41. 48 6
      models/db.go
  42. 3 3
      models/report.go
  43. 22 0
      models/report_state_record.go
  44. 11 0
      models/sandbox/request/sandbox.go
  45. 1 1
      models/sandbox/response/sandbox.go
  46. 223 15
      models/sandbox/sandbox.go
  47. 163 9
      models/sandbox/sandbox_classify.go
  48. 345 0
      models/smart_report/smart_report.go
  49. 122 0
      models/smart_report/smart_report_save_log.go
  50. 1 1
      models/system/sys_menu.go
  51. 625 229
      routers/commentsRouter.go
  52. 20 2
      routers/router.go
  53. 52 0
      services/data/base_bridge.go
  54. 24 0
      services/data/base_edb_lib.go
  55. 1 1
      services/data/chart_info_elastic.go
  56. 9 6
      services/data/correlation/chart_info.go
  57. 28 4
      services/data/edb_classify.go
  58. 232 187
      services/data/edb_info.go
  59. 5 0
      services/data/edb_info_calculate.go
  60. 426 0
      services/data/excel/custom_analysis.go
  61. 547 0
      services/data/excel/custom_analysis_edb.go
  62. 20 13
      services/data/excel/excel_info.go
  63. 275 0
      services/data/excel/excel_op.go
  64. 331 0
      services/data/jiayue_index.go
  65. 62 2
      services/elastic.go
  66. 379 0
      services/excel/excel_to_lucky_sheet.go
  67. 71 27
      services/excel/lucky_sheet.go
  68. 49 0
      services/excel/lucky_sheet_excel.go
  69. 52 0
      services/excel/xml.go
  70. 63 0
      services/excel_info.go
  71. 8 6
      services/minio.go
  72. 516 299
      services/sandbox/sandbox.go
  73. 131 0
      services/smart_report/smart_report.go
  74. 21 13
      utils/config.go
  75. 15 2
      utils/constants.go

+ 2 - 1
.gitignore

@@ -17,4 +17,5 @@
 /static/images/*.svg
 eta_api.exe
 eta_api.exe~
-/static/tmpFile/*
+/static/tmpFile/*
+/etalogs

+ 728 - 0
controllers/data_manage/chart_framework.go

@@ -0,0 +1,728 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// ChartFrameworkController 图库框架
+type ChartFrameworkController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 列表
+// @Description 列表
+// @Param   AdminId		query	int		false	"创建人ID"
+// @Param   Visibility	query	int		false	"范围: 0-所有; 1-私有; 2-公开"
+// @Param   Keyword		query	string	false	"关键词"
+// @Success 200 Ret=200 获取成功
+// @router /list [get]
+func (this *ChartFrameworkController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	adminId, _ := this.GetInt("AdminId")
+	visibility, _ := this.GetInt("Visibility")
+	if visibility == 1 && adminId <= 0 {
+		adminId = sysUser.AdminId
+	}
+	keyword := this.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+
+	frameworkOb := new(data_manage.ChartFramework)
+	cond := ``
+	pars := make([]interface{}, 0)
+	if adminId > 0 {
+		cond += fmt.Sprintf(` AND %s = ?`, data_manage.ChartFrameworkColumns.AdminId)
+		pars = append(pars, adminId)
+	}
+	if keyword != "" {
+		cond += fmt.Sprintf(` AND %s LIKE ?`, data_manage.ChartFrameworkColumns.FrameworkName)
+		pars = append(pars, "%"+keyword+"%")
+	}
+	if visibility > 0 {
+		visibilityArr := map[int]int{1: 0, 2: 1}
+		cond += fmt.Sprintf(` AND %s = ?`, data_manage.ChartFrameworkColumns.IsPublic)
+		pars = append(pars, visibilityArr[visibility])
+	}
+
+	orderRule := `sort ASC, create_time DESC`
+	list, e := frameworkOb.GetItemsByCondition(cond, pars, []string{}, orderRule)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取框架列表失败, Err: " + e.Error()
+		return
+	}
+	resp := make([]*data_manage.ChartFrameworkItem, 0)
+	for _, v := range list {
+		t := data_manage.FormatChartFramework2Item(v)
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// PublicMenu
+// @Title 公开框架目录
+// @Description 公开框架目录
+// @Success 200 Ret=200 获取成功
+// @router /public_menu [get]
+func (this *ChartFrameworkController) PublicMenu() {
+	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
+	}
+
+	frameworkOb := new(data_manage.ChartFramework)
+	cond := fmt.Sprintf(` AND %s = ?`, data_manage.ChartFrameworkColumns.IsPublic)
+	pars := make([]interface{}, 0)
+	pars = append(pars, 1)
+	orderRule := `public_time ASC, sort ASC, create_time DESC`
+	list, e := frameworkOb.GetItemsByCondition(cond, pars, []string{}, orderRule)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取公开框架失败, Err: " + e.Error()
+		return
+	}
+
+	userExist := make(map[int]bool)
+	userFrameworks := make(map[int][]*data_manage.ChartFrameworkItem)
+	resp := make([]*data_manage.ChartFrameworkPublicMenuItem, 0)
+	for _, v := range list {
+		if !userExist[v.AdminId] {
+			u := new(data_manage.ChartFrameworkPublicMenuItem)
+			u.AdminId = v.AdminId
+			u.MenuName = fmt.Sprintf("%s的框架", v.AdminName)
+			resp = append(resp, u)
+			userExist[v.AdminId] = true
+		}
+		t := data_manage.FormatChartFramework2Item(v)
+		if userFrameworks[v.AdminId] == nil {
+			userFrameworks[v.AdminId] = make([]*data_manage.ChartFrameworkItem, 0)
+		}
+		userFrameworks[v.AdminId] = append(userFrameworks[v.AdminId], t)
+	}
+	for _, v := range resp {
+		v.Frameworks = userFrameworks[v.AdminId]
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增框架
+// @Description 新增框架
+// @Param	request	body data_manage.ChartFrameworkAddReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /add [post]
+func (this *ChartFrameworkController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 参数校验
+	var req data_manage.ChartFrameworkAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	req.FrameworkName = strings.TrimSpace(req.FrameworkName)
+	if req.FrameworkName == "" {
+		br.Msg = "框架名称不可为空"
+		return
+	}
+
+	// 重名校验
+	{
+		ob := new(data_manage.ChartFramework)
+		cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, data_manage.ChartFrameworkColumns.FrameworkName, data_manage.ChartFrameworkColumns.AdminId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.FrameworkName, sysUser.AdminId)
+		exist, e := ob.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名框架失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "框架名称已存在"
+			return
+		}
+	}
+
+	now := time.Now().Local()
+	frameworkCode := utils.MD5(fmt.Sprint(now.UnixMilli()))
+	item := new(data_manage.ChartFramework)
+	item.FrameworkName = req.FrameworkName
+	item.FrameworkCode = frameworkCode
+	item.FrameworkImg = req.FrameworkImg
+	item.FrameworkContent = req.FrameworkContent
+	item.AdminId = sysUser.AdminId
+	item.AdminName = sysUser.RealName
+	item.CreateTime = now
+	item.ModifyTime = now
+	nodes := make([]*data_manage.ChartFrameworkNode, 0)
+	if len(req.Nodes) > 0 {
+		for _, v := range req.Nodes {
+			if v.MyChartClassifyId <= 0 {
+				continue
+			}
+			t := new(data_manage.ChartFrameworkNode)
+			t.FrameworkName = req.FrameworkName
+			t.NodeName = v.NodeName
+			t.MyChartClassifyId = v.MyChartClassifyId
+			t.CreateTime = now
+			nodes = append(nodes, t)
+		}
+	}
+	if e := item.CreateFrameworkAndNodes(item, nodes); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增框架及节点失败, Err: " + e.Error()
+		return
+	}
+	detail := data_manage.FormatChartFramework2Item(item)
+
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.IsAddLog = true
+}
+
+// Edit
+// @Title 编辑框架
+// @Description 编辑框架
+// @Param	request	body data_manage.ChartFrameworkEditReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /edit [post]
+func (this *ChartFrameworkController) Edit() {
+	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.ChartFrameworkEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ChartFrameworkId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartFrameworkId: %d", req.ChartFrameworkId)
+		return
+	}
+	req.FrameworkName = strings.TrimSpace(req.FrameworkName)
+	if req.FrameworkName == "" {
+		br.Msg = "框架名称不可为空"
+		return
+	}
+
+	frameworkOb := new(data_manage.ChartFramework)
+	item, e := frameworkOb.GetItemById(req.ChartFrameworkId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "框架不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取框架失败, Err: " + e.Error()
+		return
+	}
+
+	// 重名校验
+	{
+		ob := new(data_manage.ChartFramework)
+		cond := fmt.Sprintf(` AND %s <> ? AND %s = ? AND %s = ?`, ob.PrimaryId(), data_manage.ChartFrameworkColumns.FrameworkName, data_manage.ChartFrameworkColumns.AdminId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ChartFrameworkId, req.FrameworkName, sysUser.AdminId)
+		exist, e := ob.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名框架失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "框架名称已存在"
+			return
+		}
+	}
+
+	now := time.Now().Local()
+	item.FrameworkName = req.FrameworkName
+	item.FrameworkImg = req.FrameworkImg
+	item.FrameworkContent = req.FrameworkContent
+	item.ModifyTime = now
+	updateCols := []string{"FrameworkName", "FrameworkImg", "FrameworkContent", "ModifyTime"}
+	nodes := make([]*data_manage.ChartFrameworkNode, 0)
+	if len(req.Nodes) > 0 {
+		for _, v := range req.Nodes {
+			if v.MyChartClassifyId <= 0 {
+				continue
+			}
+			t := new(data_manage.ChartFrameworkNode)
+			t.ChartFrameworkId = req.ChartFrameworkId
+			t.FrameworkName = req.FrameworkName
+			t.NodeName = v.NodeName
+			t.MyChartClassifyId = v.MyChartClassifyId
+			t.CreateTime = now
+			nodes = append(nodes, t)
+		}
+	}
+	if e := item.EditFrameworkAndNodes(item, updateCols, nodes); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "编辑框架及节点失败, Err: " + e.Error()
+		return
+	}
+	detail := data_manage.FormatChartFramework2Item(item)
+
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.IsAddLog = true
+}
+
+// Remove
+// @Title 删除框架
+// @Description 删除视频
+// @Param	request	body data_manage.ChartFrameworkRemoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /remove [post]
+func (this *ChartFrameworkController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 参数校验
+	var req data_manage.ChartFrameworkRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ChartFrameworkId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartFrameworkId: %d", req.ChartFrameworkId)
+		return
+	}
+
+	ob := new(data_manage.ChartFramework)
+	item, e := ob.GetItemById(req.ChartFrameworkId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取框架失败, Err: " + e.Error()
+		return
+	}
+	if sysUser.RoleTypeCode != utils.ROLE_TYPE_CODE_ADMIN && item.AdminId != sysUser.AdminId {
+		br.Msg = "无权操作"
+		return
+	}
+
+	if e := item.RemoveFrameworkAndNodes(req.ChartFrameworkId); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除框架失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.IsAddLog = true
+}
+
+// Rename
+// @Title 重命名框架
+// @Description 重命名框架
+// @Param	request	body data_manage.ChartFrameworkRenameReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /rename [post]
+func (this *ChartFrameworkController) Rename() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 参数校验
+	var req data_manage.ChartFrameworkRenameReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ChartFrameworkId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartFrameworkId: %d", req.ChartFrameworkId)
+		return
+	}
+	req.FrameworkName = strings.TrimSpace(req.FrameworkName)
+	if req.FrameworkName == "" {
+		br.Msg = "框架名称不可为空"
+		return
+	}
+
+	frameworkOb := new(data_manage.ChartFramework)
+	item, e := frameworkOb.GetItemById(req.ChartFrameworkId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "框架不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取框架失败, Err: " + e.Error()
+		return
+	}
+
+	// 重名校验
+	{
+		ob := new(data_manage.ChartFramework)
+		cond := fmt.Sprintf(` AND %s <> ? AND %s = ? AND %s = ?`, ob.PrimaryId(), data_manage.ChartFrameworkColumns.FrameworkName, data_manage.ChartFrameworkColumns.AdminId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ChartFrameworkId, req.FrameworkName, sysUser.AdminId)
+		exist, e := ob.GetItemByCondition(cond, pars)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取重名框架失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "框架名称已存在"
+			return
+		}
+	}
+
+	now := time.Now().Local()
+	item.FrameworkName = req.FrameworkName
+	item.ModifyTime = now
+	updateCols := []string{"FrameworkName", "ModifyTime"}
+	if e := item.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "框架重命名失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// EditPublic
+// @Title 公开/隐藏框架
+// @Description 公开/隐藏框架
+// @Param	request	body data_manage.ChartFrameworkEditPublicReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /edit_public [post]
+func (this *ChartFrameworkController) EditPublic() {
+	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.ChartFrameworkEditPublicReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ChartFrameworkId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartFrameworkId: %d", req.ChartFrameworkId)
+		return
+	}
+	if req.IsPublic != 0 && req.IsPublic != 1 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IsPublic: %d", req.IsPublic)
+		return
+	}
+
+	frameworkOb := new(data_manage.ChartFramework)
+	item, e := frameworkOb.GetItemById(req.ChartFrameworkId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "框架不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取框架失败, Err: " + e.Error()
+		return
+	}
+
+	now := time.Now().Local()
+	updateCols := []string{"IsPublic", "PublicTime", "ModifyTime"}
+	item.IsPublic = req.IsPublic
+	if req.IsPublic == 1 {
+		item.PublicTime = time.Now().Local()
+	} else {
+		item.PublicTime = time.Time{}
+	}
+	item.ModifyTime = now
+	if e := item.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新框架是否公开失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Move
+// @Title 移动排序
+// @Description 移动排序
+// @Param	request	body data_manage.ChartFrameworkMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /move [post]
+func (this *ChartFrameworkController) Move() {
+	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.ChartFrameworkMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.ChartFrameworkId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartFrameworkId: %d", req.ChartFrameworkId)
+		return
+	}
+
+	frameworkOb := new(data_manage.ChartFramework)
+	item, e := frameworkOb.GetItemById(req.ChartFrameworkId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "框架不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取框架失败, Err: " + e.Error()
+		return
+	}
+
+	updateCols := make([]string, 0)
+	// 上一个兄弟节点
+	if req.PrevChartFrameworkId > 0 {
+		prev, e := frameworkOb.GetItemById(req.PrevChartFrameworkId)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取上一个兄弟节点失败, Err: " + e.Error()
+			return
+		}
+
+		// 两个兄弟节点之间
+		if req.NextChartFrameworkId > 0 {
+			next, e := frameworkOb.GetItemById(req.PrevChartFrameworkId)
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取下一个兄弟节点失败, Err: " + e.Error()
+				return
+			}
+			// 如果上一个与下一个排序权重是一致的, 那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2, 自己变成上一个兄弟的排序权重+1
+			if prev.Sort == next.Sort || prev.Sort == item.Sort {
+				strUpdate := `sort + 2`
+				_ = data_manage.UpdateChartFrameworkSort(sysUser.AdminId, prev.ChartFrameworkId, prev.Sort, strUpdate)
+			} else {
+				// 如果下一个排序权重正好是上一个节点的下一层, 那么需要再加一层了
+				if next.Sort-prev.Sort == 1 {
+					//变更兄弟节点的排序
+					strUpdate := `sort + 1`
+					_ = data_manage.UpdateChartFrameworkSort(sysUser.AdminId, 0, prev.Sort, strUpdate)
+				}
+			}
+		}
+
+		// 上一个兄弟节点sort+1
+		item.Sort = prev.Sort + 1
+		item.ModifyTime = time.Now()
+		updateCols = append(updateCols, "Sort", "ModifyTime")
+	} else {
+		first, err := data_manage.GetFirstChartFramework(sysUser.AdminId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取我的图库框架排首位的数据失败, Err:" + err.Error()
+			return
+		}
+		if first != nil && first.Sort == 0 {
+			strUpdate := ` sort + 1 `
+			_ = data_manage.UpdateChartFrameworkSort(sysUser.AdminId, first.ChartFrameworkId-1, 0, strUpdate)
+		}
+
+		// 排首位
+		item.Sort = 0
+		item.ModifyTime = time.Now()
+		updateCols = append(updateCols, "Sort", "ModifyTime")
+	}
+
+	if len(updateCols) > 0 {
+		if e := item.Update(updateCols); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新框架排序失败, Err: " + e.Error()
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 框架详情
+// @Description 框架详情
+// @Param   ChartFrameworkId  query  int  true  "框架ID"
+// @Success 200 Ret=200 操作成功
+// @router /detail [get]
+func (this *ChartFrameworkController) Detail() {
+	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
+	}
+	frameworkId, _ := this.GetInt("ChartFrameworkId")
+	if frameworkId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartFrameworkId: %d", frameworkId)
+		return
+	}
+
+	frameworkOb := new(data_manage.ChartFramework)
+	item, e := frameworkOb.GetItemById(frameworkId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "框架不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取框架失败, Err: " + e.Error()
+		return
+	}
+	detail := data_manage.FormatChartFramework2Item(item)
+
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 1 - 0
controllers/data_manage/edb_classify.go

@@ -295,6 +295,7 @@ func (this *EdbClassifyController) EditEdbClassify() {
 	br.Ret = 200
 	br.Msg = "保存成功"
 	br.Success = true
+	br.IsAddLog = true
 }
 
 // @Title 删除检测接口

+ 71 - 3
controllers/data_manage/edb_info.go

@@ -1553,8 +1553,76 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				isAdd = true
 			}
 		} else {
-			br.Msg = "无效的数据来源"
-			return
+			// 代码中没有的来源那么从edb_source中找是否有对应的
+			sourceItem := data_manage.EdbSourceIdMap[source]
+			if sourceItem == nil {
+				br.Msg = "无效指标来源"
+				return
+			}
+
+			// 获取指标数据
+			dataList, e := data_manage.GetEdbDataAllByEdbCode(edbCode, source, utils.EDB_DATA_LIMIT)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取指标数据失败, Err: " + e.Error()
+				return
+			}
+			if len(dataList) > 0 {
+				searchItem.EdbCode = edbCode
+				searchItem.DataList = dataList
+				// 获取最大最小值
+				edbInfoMaxAndMinInfo, e := data_manage.GetEdbInfoMaxAndMinInfo(source, edbCode)
+				if e != nil && e.Error() != utils.ErrNoRow() {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取数据失败, GetEdbInfoMaxAndMinInfo Err:" + e.Error()
+					return
+				}
+				if edbInfoMaxAndMinInfo != nil {
+					searchItem.StartDate = edbInfoMaxAndMinInfo.MinDate
+					searchItem.EndDate = edbInfoMaxAndMinInfo.MaxDate
+				}
+			} else {
+				// 新增指标数据
+				addRes, e := data.AddEdbData(source, edbCode)
+				if e != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取失败,Err:" + e.Error()
+					return
+				}
+				if addRes == nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "新增指标数据失败, addRes nil"
+					return
+				}
+				if addRes.Ret != 200 {
+					br.Msg = "未搜索到该指标, " + addRes.Msg
+					//br.ErrMsg = addRes.ErrMsg + ";EdbCode:" + edbCode
+					br.Success = true
+					return
+				}
+				isAdd = true
+			}
+
+			// 指标来源于桥接服务: 桥接服务获取指标取频度信息等
+			if sourceItem.BridgeFlag != "" {
+				bridgeOb := data.InitBridgeOB(sourceItem.BridgeFlag)
+				if bridgeOb != nil {
+					var r data.GetIndexFromBridgeReq
+					r.IndexCode = edbCode
+					r.Source = sourceItem.EdbSourceId
+					r.SourceExtend = sourceItem.SourceExtend
+					r.IndexCodeRequired = sourceItem.EdbCodeRequired
+					bridgeIndex, e := bridgeOb.GetIndex(r)
+					if e != nil {
+						br.Msg = "获取失败"
+						br.ErrMsg = "桥接服务获取指标信息失败, Err: " + e.Error()
+						return
+					}
+					searchItem.EdbName = bridgeIndex.IndexName
+					searchItem.Frequency = bridgeIndex.Frequency
+					searchItem.Unit = bridgeIndex.Unit
+				}
+			}
 		}
 		if isAdd {
 			dataList, err := data_manage.GetEdbDataAllByEdbCode(edbCode, source, utils.EDB_DATA_LIMIT)
@@ -4324,7 +4392,7 @@ func (this *EdbInfoController) EdbChartAdminList() {
 			if v1 == depList[k].RealName && v1 != "" {
 				item := &company.DepartmentGroupAdmins{
 					AdminId:  "group_" + strconv.Itoa(k),
-					RealName: strings.Replace(k1,v1,"",-1),
+					RealName: strings.Replace(k1, v1, "", -1),
 				}
 				depList[k].ChildrenList = append(depList[k].ChildrenList, item)
 			}

+ 591 - 0
controllers/data_manage/excel/custom_analysis.go

@@ -0,0 +1,591 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	excelModel "eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
+	"eta/eta_api/models/data_manage/excel/response"
+	"eta/eta_api/services"
+	"eta/eta_api/services/data/excel"
+	"eta/eta_api/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// CustomAnalysisController 自定义分析
+type CustomAnalysisController struct {
+	controllers.BaseAuthController
+}
+
+// ExcelByName
+// @Title 根据excel名称获取表格详情(基础信息+第一页初始化数据)
+// @Description 根据excel名称获取表格详情(基础信息+第一页初始化数据)
+// @Param   ExcelName   query   string  true       "搜索关键词"
+// @Success 200 {object} response.ExcelListResp
+// @router /excel_by_name [get]
+func (c *CustomAnalysisController) ExcelByName() {
+	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
+	}
+
+	excelName := c.GetString("ExcelName")
+	if excelName == `` {
+		br.Msg = "请选择表格"
+		br.ErrMsg = "ExcelName未传"
+		br.IsSendEmail = false
+		return
+	}
+
+	resp := response.FindExcelInfoResp{}
+
+	excelName = utils.TrimLRStr(excelName)
+	// 获取数据详情
+	excelDetail, err := excelModel.GetNoContentExcelInfoByName(excelName, utils.CUSTOM_ANALYSIS_TABLE)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			br.Data = resp
+			return
+		}
+		br.Msg = "获取表格事变"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	resp.IsFind = true
+	resp.ExcelInfo = response.FindExcelInfo{
+		ExcelInfoId:     excelDetail.ExcelInfoId,
+		Source:          excelDetail.Source,
+		ExcelType:       excelDetail.ExcelType,
+		ExcelName:       excelDetail.ExcelName,
+		UniqueCode:      excelDetail.UniqueCode,
+		ExcelClassifyId: excelDetail.ExcelClassifyId,
+		SysUserId:       excelDetail.SysUserId,
+		SysUserRealName: excelDetail.SysUserRealName,
+		ExcelImage:      excelDetail.ExcelImage,
+		FileUrl:         excelDetail.FileUrl,
+		Sort:            excelDetail.Sort,
+		ModifyTime:      excelDetail.ModifyTime,
+		CreateTime:      excelDetail.CreateTime,
+		Button:          excel.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source),
+	}
+
+	if excelDetail != nil {
+		sheetList, err := excelModel.GetAllSheetItemList(excelDetail.ExcelInfoId)
+		if err != nil {
+			br.Msg = "获取sheet失败"
+			br.ErrMsg = "获取sheet失败,err:" + err.Error()
+			return
+		}
+
+		resp.SheetList = sheetList
+	}
+
+	//resp := response.ExcelListResp{
+	//	Paging: page,
+	//	List:   list,
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增表格接口
+// @Description 新增表格接口
+// @Param	request	body request.AddExcelInfoReq true "type json string"
+// @Success 200 {object} response.AddExcelInfoResp
+// @router /add [post]
+func (c *CustomAnalysisController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EXCEL_TABLE_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
+		return
+	}
+	var req request.AddExcelInfoReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.ExcelName = strings.Trim(req.ExcelName, " ")
+	if req.ExcelName == "" {
+		br.Msg = "请填写表格名称!"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取是否存在该表格名称
+	{
+		var condition string
+		var pars []interface{}
+		condition += " AND source=? "
+		pars = append(pars, utils.CUSTOM_ANALYSIS_TABLE)
+
+		condition += " AND excel_name=? "
+		pars = append(pars, req.ExcelName)
+
+		count, err := excelModel.GetExcelInfoCountByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "判断表格名称是否存在失败"
+			br.ErrMsg = "判断表格名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "表格名称已存在,请重新填写表格名称"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	if req.ExcelClassifyId <= 0 {
+		br.Msg = "分类参数错误!"
+		br.IsSendEmail = false
+		return
+	}
+
+	excelInfo, err, errMsg, isSendEmail := excel.AddCustomAnalysisTable(utils.TrimLRStr(req.ExcelName), req.Content, req.ExcelImage, req.ExcelClassifyId, sysUser)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 更新excel下载地址(默认的EXCEL需要更新,自定义表格不需要更新)
+	//if req.Source == 1 {
+	//	go UpdateExcelInfoFileUrl(excelInfo)
+	//}
+	//
+	resp := new(response.AddExcelInfoResp)
+	resp.ExcelInfoId = excelInfo.ExcelInfoId
+	resp.UniqueCode = excelInfo.UniqueCode
+
+	// 生成excel文件
+	go excel.UpdateExcelInfoFileUrl(excelInfo)
+
+	//新增操作日志
+	//{
+	//	excelLog := &data_manage.ExcelInfoLog{
+	//		//ExcelInfoLogId:  0,
+	//		ExcelInfoId:     excelInfo.ExcelInfoId,
+	//		ExcelName:       req.ExcelName,
+	//		ExcelClassifyId: req.ExcelClassifyId,
+	//		SysUserId:       sysUser.AdminId,
+	//		SysUserRealName: sysUser.RealName,
+	//		UniqueCode:      excelInfo.UniqueCode,
+	//		CreateTime:      time.Now(),
+	//		Content:         string(c.Ctx.Input.RequestBody),
+	//		Status:          "新增表格",
+	//		Method:          c.Ctx.Input.URI(),
+	//	}
+	//	go data_manage.AddExcelInfoLog(excelLog)
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = false //数据量太大了,不写入日志吧
+}
+
+// Save
+// @Title 保存表格接口
+// @Description 保存表格接口
+// @Param	request	body request.AddExcelInfoReq true "type json string"
+// @Success 200 {object} response.AddExcelInfoResp
+// @router /save [post]
+func (c *CustomAnalysisController) Save() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EXCEL_TABLE_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
+		return
+	}
+	var req request.SaveExcelInfoReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.ExcelName = strings.Trim(req.ExcelName, " ")
+	if req.ExcelName == "" {
+		br.Msg = "请填写表格名称!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ExcelInfoId <= 0 {
+		br.Msg = "请选择excel!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ExcelClassifyId <= 0 {
+		br.Msg = "分类参数错误!"
+		br.IsSendEmail = false
+		return
+	}
+
+	excelInfo, err := excelModel.GetExcelInfoById(req.ExcelInfoId)
+	if err != nil {
+		br.Msg = "找不到该EXCEL!"
+		br.ErrMsg = "找不到该EXCEL!err:" + err.Error()
+		return
+	}
+
+	if excelInfo.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		br.Msg = "EXCEL异常!"
+		br.IsSendEmail = false
+		return
+	}
+
+	err, errMsg, isSendEmail := excel.SaveCustomAnalysisTable(excelInfo, utils.TrimLRStr(req.ExcelName), req.Content, req.ExcelImage, req.ExcelClassifyId, req.OpSheetList)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 更新excel下载地址(默认的EXCEL需要更新,自定义表格不需要更新)
+	//if req.Source == 1 {
+	//	go UpdateExcelInfoFileUrl(excelInfo)
+	//}
+	//
+	resp := new(response.AddExcelInfoResp)
+	resp.ExcelInfoId = excelInfo.ExcelInfoId
+	resp.UniqueCode = excelInfo.UniqueCode
+
+	// 生成excel文件
+	go excel.UpdateExcelInfoFileUrl(excelInfo)
+
+	//新增操作日志
+	//{
+	//	excelLog := &data_manage.ExcelInfoLog{
+	//		//ExcelInfoLogId:  0,
+	//		ExcelInfoId:     excelInfo.ExcelInfoId,
+	//		ExcelName:       req.ExcelName,
+	//		ExcelClassifyId: req.ExcelClassifyId,
+	//		SysUserId:       sysUser.AdminId,
+	//		SysUserRealName: sysUser.RealName,
+	//		UniqueCode:      excelInfo.UniqueCode,
+	//		CreateTime:      time.Now(),
+	//		Content:         string(c.Ctx.Input.RequestBody),
+	//		Status:          "新增表格",
+	//		Method:          c.Ctx.Input.URI(),
+	//	}
+	//	go data_manage.AddExcelInfoLog(excelLog)
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = false //数据量太大了,不写入日志吧
+}
+
+// BaseExcelDetail
+// @Title 根据excel名称获取表格详情(基础信息+第一页初始化数据)
+// @Description 根据excel名称获取表格详情(基础信息+第一页初始化数据)
+// @Param   UniqueCode   query   string  true       "excel唯一编码"
+// @Success 200 {object} response.ExcelListResp
+// @router /excel/base [get]
+func (c *CustomAnalysisController) BaseExcelDetail() {
+	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
+	}
+
+	uniqueCode := c.GetString("UniqueCode")
+	if uniqueCode == `` {
+		br.Msg = "请选择表格"
+		br.ErrMsg = "UniqueCode未传"
+		br.IsSendEmail = false
+		return
+	}
+
+	resp := response.FindExcelInfoResp{}
+
+	// 获取数据详情
+	excelDetail, err := excelModel.GetNoContentExcelInfoByUniqueCode(uniqueCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			br.Data = resp
+			return
+		}
+		br.Msg = "获取表格事变"
+		br.ErrMsg = err.Error()
+		return
+	}
+	// 编辑状态
+	markStatus, err := services.UpdateExcelEditMark(excelDetail.ExcelInfoId, sysUser.AdminId, 2, sysUser.RealName)
+	if err != nil {
+		br.Msg = "查询标记状态失败"
+		br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+		return
+	}
+
+
+	resp.IsFind = true
+	resp.ExcelInfo = response.FindExcelInfo{
+		ExcelInfoId:     excelDetail.ExcelInfoId,
+		Source:          excelDetail.Source,
+		ExcelType:       excelDetail.ExcelType,
+		ExcelName:       excelDetail.ExcelName,
+		UniqueCode:      excelDetail.UniqueCode,
+		ExcelClassifyId: excelDetail.ExcelClassifyId,
+		SysUserId:       excelDetail.SysUserId,
+		SysUserRealName: excelDetail.SysUserRealName,
+		ExcelImage:      excelDetail.ExcelImage,
+		FileUrl:         excelDetail.FileUrl,
+		Sort:            excelDetail.Sort,
+		ModifyTime:      excelDetail.ModifyTime,
+		CreateTime:      excelDetail.CreateTime,
+		Button:          excel.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source),
+	}
+	if markStatus.Status == 0 {
+		resp.ExcelInfo.CanEdit = true
+	} else {
+		resp.ExcelInfo.Editor = markStatus.Editor
+	}
+	if excelDetail != nil {
+		sheetList, err := excelModel.GetAllSheetItemList(excelDetail.ExcelInfoId)
+		if err != nil {
+			br.Msg = "获取sheet失败"
+			br.ErrMsg = "获取sheet失败,err:" + err.Error()
+			return
+		}
+
+		if len(sheetList) > 0 {
+			sheetIdList := make([]int, 0)
+			for _, v := range sheetList {
+				sheetIdList = append(sheetIdList, v.ExcelSheetId)
+			}
+			// 获取所有sheet的第一页的数据
+			sheetDataList, err := excelModel.GetSheetDataListBySheetIdListAndPage(sheetIdList, 1)
+			if err != nil {
+				br.Msg = "获取sheet中的数据失败"
+				br.ErrMsg = "获取sheet中的数据失败,err:" + err.Error()
+				return
+			}
+
+			sheetDataMap := make(map[int]*excelModel.ExcelSheetData)
+			for _, v := range sheetDataList {
+				sheetDataMap[v.ExcelSheetId] = v
+			}
+
+			for k, v := range sheetList {
+				sheetData, ok := sheetDataMap[v.ExcelSheetId]
+				if !ok {
+					continue
+				}
+				v.Data = sheetData
+				sheetList[k] = v
+			}
+
+		}
+
+		// TODO 合并单元格信息、计算公式
+
+		resp.SheetList = sheetList
+	}
+
+	//resp := response.ExcelListResp{
+	//	Paging: page,
+	//	List:   list,
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ExcelDataList
+// @Title 根据excel名称获取表格详情(基础信息+第一页初始化数据)
+// @Description 根据excel名称获取表格详情(基础信息+第一页初始化数据)
+// @Param   UniqueCode   query   string  true       "excel唯一编码"
+// @Param   Page   query   int  true       "页码"
+// @Success 200 {object} response.ExcelListResp
+// @router /excel/data [get]
+func (c *CustomAnalysisController) ExcelDataList() {
+	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
+	}
+
+	uniqueCode := c.GetString("UniqueCode")
+	if uniqueCode == `` {
+		br.Msg = "请选择表格"
+		br.ErrMsg = "UniqueCode未传"
+		br.IsSendEmail = false
+		return
+	}
+
+	page, _ := c.GetInt("Page")
+	if page <= 0 {
+		br.Msg = "页码异常"
+		br.ErrMsg = "页码异常"
+		br.IsSendEmail = false
+		return
+	}
+
+	sheetList := make([]*excelModel.SheetItem, 0)
+	// 获取数据详情
+	excelDetail, err := excelModel.GetNoContentExcelInfoByUniqueCode(uniqueCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			br.Data = sheetList
+			return
+		}
+		br.Msg = "获取表格事变"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if excelDetail.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		br.Msg = "excel异常"
+		br.ErrMsg = "excel异常"
+		br.IsSendEmail = false
+		return
+	}
+
+	if excelDetail != nil {
+		sheetList, err = excelModel.GetAllNoConfigSheetItemList(excelDetail.ExcelInfoId)
+		if err != nil {
+			br.Msg = "获取sheet失败"
+			br.ErrMsg = "获取sheet失败,err:" + err.Error()
+			return
+		}
+
+		if len(sheetList) > 0 {
+			sheetIdList := make([]int, 0)
+			for _, v := range sheetList {
+				sheetIdList = append(sheetIdList, v.ExcelSheetId)
+			}
+			// 获取所有sheet的第一页的数据
+			sheetDataList, err := excelModel.GetSheetDataListBySheetIdListAndPage(sheetIdList, page)
+			if err != nil {
+				br.Msg = "获取sheet中的数据失败"
+				br.ErrMsg = "获取sheet中的数据失败,err:" + err.Error()
+				return
+			}
+
+			sheetDataMap := make(map[int]*excelModel.ExcelSheetData)
+			for _, v := range sheetDataList {
+				sheetDataMap[v.ExcelSheetId] = v
+			}
+
+			for k, v := range sheetList {
+				sheetData, ok := sheetDataMap[v.ExcelSheetId]
+				if !ok {
+					continue
+				}
+				v.Data = sheetData
+				sheetList[k] = v
+			}
+
+		}
+
+		// TODO 合并单元格信息、计算公式
+
+	}
+
+	//resp := response.ExcelListResp{
+	//	Paging: page,
+	//	List:   list,
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = sheetList
+}

+ 469 - 0
controllers/data_manage/excel/custom_analysis_edb.go

@@ -0,0 +1,469 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	excelModel "eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/excel"
+	"eta/eta_api/utils"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// EdbList
+// @Title 指标列表
+// @Description 指标列表
+// @Param   ExcelInfoId   query   int  true       "excel的id"
+// @Success 200 {object} []excel.ExcelEdbMappingItem
+// @router /edb/list [get]
+func (c *CustomAnalysisController) EdbList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	excelInfoId, _ := c.GetInt("ExcelInfoId")
+	if excelInfoId <= 0 {
+		br.Msg = "请选择excel"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取excel表详情
+	excelInfo, err := excelModel.GetExcelInfoById(excelInfoId)
+	if err != nil {
+		br.Msg = "找不到该EXCEL!"
+		br.ErrMsg = "找不到该EXCEL!err:" + err.Error()
+		return
+	}
+
+	if excelInfo.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		br.Msg = "EXCEL异常!"
+		br.IsSendEmail = false
+		return
+	}
+
+	list, err := excelModel.GetAllExcelEdbMappingItemByExcelInfoId(excelInfo.ExcelInfoId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	for k, v := range list {
+		var tmpCalculateFormula excelModel.CalculateFormula
+		err = json.Unmarshal([]byte(v.CalculateFormula), &tmpCalculateFormula)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "公式转换失败,Err:" + err.Error()
+			return
+		}
+		v.DateSequenceStr = tmpCalculateFormula.DateSequenceStr
+		v.DataSequenceStr = tmpCalculateFormula.DataSequenceStr
+		list[k] = v
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// AddEdb
+// @Title 新增指标接口
+// @Description 新增指标接口
+// @Param	request	body request.AddEdb true "type json string"
+// @Success 200 {object} data_manage.AddEdbInfoResp
+// @router /edb/add [post]
+func (c *CustomAnalysisController) AddEdb() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EXCEL_TABLE_INFO_ADD_EDB_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
+		return
+	}
+	var req request.AddEdb
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.EdbName = strings.Trim(req.EdbName, " ")
+	if req.EdbName == "" {
+		br.Msg = "请填写指标名称!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ExcelInfoId <= 0 {
+		br.Msg = "请选择excel!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择指标分类!"
+		br.IsSendEmail = false
+		return
+	}
+
+	excelInfo, err := excelModel.GetExcelInfoById(req.ExcelInfoId)
+	if err != nil {
+		br.Msg = "找不到该EXCEL!"
+		br.ErrMsg = "找不到该EXCEL!err:" + err.Error()
+		return
+	}
+
+	if excelInfo.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		br.Msg = "EXCEL异常!"
+		br.IsSendEmail = false
+		return
+	}
+
+	//excel.GetCustomAnalysisExcelData(excelInfo)
+
+	dateList, dataList, err, errMsg := excel.HandleEdbSequenceVal(req.DateSequenceVal, req.DataSequenceVal)
+	if err != nil {
+		br.Msg = "时间序列或数据序列异常!"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "时间序列或数据序列处理异常!err:" + err.Error()
+		return
+	}
+
+	type CustomAnalysisData struct {
+		ExcelInfoId int `description:"excel的id"`
+		DateList    []string
+		DataList    []float64
+	}
+
+	type Formula struct {
+		DateSequenceStr string `description:"日期序列"`
+		DataSequenceStr string `description:"数据序列"`
+	}
+
+	formulaByte, err := json.Marshal(Formula{
+		DateSequenceStr: req.DateSequenceStr,
+		DataSequenceStr: req.DataSequenceStr,
+	})
+	if err != nil {
+		br.Msg = "时间序列或数据序列配置异常!"
+		br.ErrMsg = "json序列化时间序列或数据序列异常!err:" + err.Error()
+		return
+	}
+
+	req2 := &data_manage.EdbInfoCalculateBatchSaveReqByEdbLib{
+		AdminId:    sysUser.AdminId,
+		AdminName:  sysUser.RealName,
+		EdbName:    req.EdbName,
+		Frequency:  req.Frequency,
+		Unit:       req.Unit,
+		ClassifyId: req.ClassifyId,
+		Formula:    string(formulaByte), //公式
+		Source:     utils.DATA_SOURCE_CALCULATE_ZDYFX,
+		Data: CustomAnalysisData{
+			ExcelInfoId: req.ExcelInfoId,
+			DateList:    dateList,
+			DataList:    dataList,
+		},
+	}
+
+	// 调用指标库去更新
+	reqJson, err := json.Marshal(req2)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	respItem, err := data.BatchSaveEdbCalculateData(string(reqJson))
+	if err != nil {
+		br.Msg = "新增失败"
+		br.ErrMsg = "新增失败,Err:" + err.Error()
+		return
+	}
+	if respItem.Ret != 200 {
+		br.Msg = respItem.Msg
+		br.ErrMsg = respItem.ErrMsg
+		return
+	}
+
+	resp := respItem.Data
+
+	//添加es
+	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// EditEdb
+// @Title 编辑指标接口
+// @Description 编辑指标接口
+// @Param	request	body request.EditEdb true "type json string"
+// @Success 200 {object} data_manage.AddEdbInfoResp
+// @router /edb/edit [post]
+func (c *CustomAnalysisController) EditEdb() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EXCEL_TABLE_INFO_ADD_EDB_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
+		return
+	}
+	var req request.EditEdb
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.EdbName = strings.Trim(req.EdbName, " ")
+	if req.EdbName == "" {
+		br.Msg = "请填写指标名称!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ExcelInfoId <= 0 {
+		br.Msg = "请选择excel!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.EdbInfoId <= 0 {
+		br.Msg = "请选择指标!"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择指标分类!"
+		br.IsSendEmail = false
+		return
+	}
+
+	excelInfo, err := excelModel.GetExcelInfoById(req.ExcelInfoId)
+	if err != nil {
+		br.Msg = "找不到该EXCEL!"
+		br.ErrMsg = "找不到该EXCEL!err:" + err.Error()
+		return
+	}
+
+	if excelInfo.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		br.Msg = "EXCEL异常!"
+		br.IsSendEmail = false
+		return
+	}
+
+	edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "指标已被删除,请刷新页面"
+			br.ErrMsg = "指标已被删除,请刷新页面:Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败:Err:" + err.Error()
+		return
+	}
+
+	//excel.GetCustomAnalysisExcelData(excelInfo)
+
+	dateList, dataList, err, errMsg := excel.HandleEdbSequenceVal(req.DateSequenceVal, req.DataSequenceVal)
+	if err != nil {
+		br.Msg = "时间序列或数据序列异常!"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "时间序列或数据序列处理异常!err:" + err.Error()
+		return
+	}
+
+	type CustomAnalysisData struct {
+		ExcelInfoId int `description:"excel的id"`
+		DateList    []string
+		DataList    []float64
+	}
+
+	type Formula struct {
+		DateSequenceStr string `description:"日期序列"`
+		DataSequenceStr string `description:"数据序列"`
+	}
+
+	formulaByte, err := json.Marshal(Formula{
+		DateSequenceStr: req.DateSequenceStr,
+		DataSequenceStr: req.DataSequenceStr,
+	})
+	if err != nil {
+		br.Msg = "时间序列或数据序列配置异常!"
+		br.ErrMsg = "json序列化时间序列或数据序列异常!err:" + err.Error()
+		return
+	}
+
+	// 构造请求
+	req2 := &data_manage.EdbInfoCalculateBatchEditReqByEdbLib{
+		EdbInfoId:  req.EdbInfoId,
+		EdbName:    req.EdbName,
+		Frequency:  req.Frequency,
+		Unit:       req.Unit,
+		ClassifyId: req.ClassifyId,
+		Formula:    string(formulaByte), //公式
+		Source:     utils.DATA_SOURCE_CALCULATE_ZDYFX,
+		Data: CustomAnalysisData{
+			ExcelInfoId: req.ExcelInfoId,
+			DateList:    dateList,
+			DataList:    dataList,
+		},
+	}
+
+	// 调用指标库去更新
+	reqJson, err := json.Marshal(req2)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	respItem, err := data.BatchEditEdbCalculateData(string(reqJson))
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "编辑失败,Err:" + err.Error()
+		return
+	}
+	if respItem.Ret != 200 {
+		br.Msg = respItem.Msg
+		br.ErrMsg = respItem.ErrMsg
+		return
+	}
+
+	resp := respItem.Data
+
+	//添加es
+	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
+
+	// 修改关联的预测指标基础信息
+	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfo)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// EdbRefresh
+// @Title 指标列表
+// @Description 指标列表
+// @Param   ExcelInfoId   query   int  true       "excel的id"
+// @Success 200 {object} []excel.ExcelEdbMappingItem
+// @router /edb/refresh [get]
+func (c *CustomAnalysisController) EdbRefresh() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	excelInfoId, _ := c.GetInt("ExcelInfoId")
+	if excelInfoId <= 0 {
+		br.Msg = "请选择excel"
+		br.IsSendEmail = false
+		return
+	}
+	cacheKey := "CACHE_EXCEL_EDB_REFRESH_" + strconv.Itoa(c.SysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + c.SysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
+	// 获取excel表详情
+	excelInfo, err := excelModel.GetExcelInfoById(excelInfoId)
+	if err != nil {
+		br.Msg = "找不到该EXCEL!"
+		br.ErrMsg = "找不到该EXCEL!err:" + err.Error()
+		return
+	}
+
+	if excelInfo.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		br.Msg = "EXCEL异常!"
+		br.IsSendEmail = false
+		return
+	}
+
+	err, errMsg, isSendEmail := excel.Refresh(excelInfo)
+	if err != nil {
+		br.Msg = "刷新失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "刷新失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "刷新成功"
+}
+
+//func init() {
+//	excelInfo, err := excelModel.GetExcelInfoById(160)
+//	if err != nil {
+//		fmt.Println("查找excel失败:", err)
+//		return
+//	}
+//	_, err, _ = excel.GenerateExcelCustomAnalysisExcel(excelInfo)
+//	if err != nil {
+//		fmt.Println("生成excel失败:", err)
+//		return
+//	}
+//}

+ 74 - 67
controllers/data_manage/excel_classify.go → controllers/data_manage/excel/excel_classify.go

@@ -1,12 +1,13 @@
-package data_manage
+package excel
 
 import (
 	"encoding/json"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
-	"eta/eta_api/models/data_manage"
-	"eta/eta_api/models/data_manage/request"
-	"eta/eta_api/models/data_manage/response"
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
+	response2 "eta/eta_api/models/data_manage/excel/response"
+	excel2 "eta/eta_api/services/data/excel"
 	"eta/eta_api/utils"
 	"strconv"
 	"time"
@@ -20,6 +21,8 @@ type ExcelClassifyController struct {
 // List
 // @Title excel表格分类列表
 // @Description excel表格分类列表接口
+// @Param   Source   query   int  true       "格来源,1:excel插件的表格,2:自定义表格,3:混合表格,默认:1"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
 // @Success 200 {object} response.ExcelClassifyListResp
 // @router /excel_classify/list [get]
 func (this *ExcelClassifyController) List() {
@@ -29,23 +32,34 @@ func (this *ExcelClassifyController) List() {
 		this.ServeJSON()
 	}()
 
+	source, _ := this.GetInt("Source")
+	if source <= 0 {
+		source = utils.EXCEL_DEFAULT
+	}
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showUserId := 0
+	if isShowMe {
+		showUserId = this.SysUser.AdminId
+	}
+
 	// 获取一级分类
-	rootList, err := data_manage.GetExcelClassifyByParentId(0)
+	rootList, err := excel.GetExcelClassifyByParentId(0, source)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
 
-	// 获取所有excel表格分类
-	allExcelInfo, err := data_manage.GetNoContentExcelInfoAll()
+	// 根据来源获取所有excel表格(无内容)
+	allExcelInfo, err := excel.GetNoContentExcelInfoAll(source, showUserId)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取表格信息失败,Err:" + err.Error()
 		return
 	}
 
-	ExcelInfoMap := make(map[int][]*data_manage.ExcelClassifyItems)
+	ExcelInfoMap := make(map[int][]*excel.ExcelClassifyItems)
 	for _, v := range allExcelInfo {
 		ExcelInfoMap[v.ExcelClassifyId] = append(ExcelInfoMap[v.ExcelClassifyId], v)
 	}
@@ -59,18 +73,18 @@ func (this *ExcelClassifyController) List() {
 	//		v.Children = items
 	//	}
 	//}
-	nodeAll := make([]*data_manage.ExcelClassifyItems, 0)
+	nodeAll := make([]*excel.ExcelClassifyItems, 0)
 	for _, v := range rootList {
-		var items []*data_manage.ExcelClassifyItems
+		var items []*excel.ExcelClassifyItems
 		if tmpItem, ok := ExcelInfoMap[v.ExcelClassifyId]; ok {
 			items = tmpItem
 		} else {
-			items = make([]*data_manage.ExcelClassifyItems, 0)
+			items = make([]*excel.ExcelClassifyItems, 0)
 		}
 		v.Children = items
 		nodeAll = append(nodeAll, v)
 	}
-	resp := response.ExcelClassifyListResp{
+	resp := response2.ExcelClassifyListResp{
 		AllNodes: nodeAll,
 	}
 	br.Ret = 200
@@ -82,6 +96,7 @@ func (this *ExcelClassifyController) List() {
 // ExcelClassifyItems
 // @Title 获取所有excel表格分类接口-不包含表格
 // @Description 获取所有excel表格分类接口-不包含表格
+// @Param   Source   query   int  true       "格来源,1:excel插件的表格,2:自定义表格,3:混合表格,默认:1"
 // @Success 200 {object} response.ExcelClassifyListResp
 // @router /excel_classify/items [get]
 func (this *ExcelClassifyController) ExcelClassifyItems() {
@@ -90,13 +105,18 @@ func (this *ExcelClassifyController) ExcelClassifyItems() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	rootList, err := data_manage.GetExcelClassifyByParentId(0)
+
+	source, _ := this.GetInt("Source")
+	if source <= 0 {
+		source = utils.EXCEL_DEFAULT
+	}
+	rootList, err := excel.GetExcelClassifyByParentId(0, source)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
-	resp := response.ExcelClassifyListResp{
+	resp := response2.ExcelClassifyListResp{
 		AllNodes: rootList,
 	}
 	br.Ret = 200
@@ -135,8 +155,13 @@ func (this *ExcelClassifyController) AddExcelClassify() {
 		return
 	}
 
+	source := req.Source
+	if source <= 0 {
+		source = utils.EXCEL_DEFAULT
+	}
+
 	// 获取同级分类下存在同名分类的数量
-	count, err := data_manage.GetExcelClassifyCount(req.ExcelClassifyName, req.ParentId)
+	count, err := excel.GetExcelClassifyCount(req.ExcelClassifyName, req.ParentId, source)
 	if err != nil {
 		br.Msg = "判断名称是否已存在失败"
 		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
@@ -148,14 +173,15 @@ func (this *ExcelClassifyController) AddExcelClassify() {
 		return
 	}
 	//获取该层级下最大的排序数
-	maxSort, err := data_manage.GetExcelClassifyMaxSort(req.ParentId)
+	maxSort, err := excel.GetExcelClassifyMaxSort(req.ParentId)
 
 	// 入库
 	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
-	classify := &data_manage.ExcelClassify{
+	classify := &excel.ExcelClassify{
 		//ExcelClassifyId:   0,
 		ExcelClassifyName: req.ExcelClassifyName,
 		ParentId:          req.ParentId,
+		Source:            source,
 		SysUserId:         this.SysUser.AdminId,
 		SysUserRealName:   this.SysUser.RealName,
 		Level:             req.Level + 1,
@@ -164,7 +190,7 @@ func (this *ExcelClassifyController) AddExcelClassify() {
 		CreateTime:        time.Now(),
 		ModifyTime:        time.Now(),
 	}
-	_, err = data_manage.AddExcelClassify(classify)
+	_, err = excel.AddExcelClassify(classify)
 	if err != nil {
 		br.Msg = "保存分类失败"
 		br.ErrMsg = "保存分类失败,Err:" + err.Error()
@@ -206,7 +232,7 @@ func (this *ExcelClassifyController) EditExcelClassify() {
 		return
 	}
 
-	item, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+	item, err := excel.GetExcelClassifyById(req.ExcelClassifyId)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.Msg = "获取分类信息失败,Err:" + err.Error()
@@ -222,7 +248,7 @@ func (this *ExcelClassifyController) EditExcelClassify() {
 	}
 
 	// 获取同级分类下存在同名分类的数量
-	count, err := data_manage.GetExcelClassifyCount(req.ExcelClassifyName, item.ParentId)
+	count, err := excel.GetExcelClassifyCount(req.ExcelClassifyName, item.ParentId, item.Source)
 	if err != nil {
 		br.Msg = "判断名称是否已存在失败"
 		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
@@ -278,7 +304,7 @@ func (this *ExcelClassifyController) DeleteExcelClassifyCheck() {
 	var tipsMsg string
 
 	// 校验是否存在该分类
-	ExcelClassifyInfo, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+	ExcelClassifyInfo, err := excel.GetExcelClassifyById(req.ExcelClassifyId)
 	if err != nil {
 		if err.Error() == utils.ErrNoRow() {
 			br.Msg = "该分类不存在"
@@ -298,7 +324,7 @@ func (this *ExcelClassifyController) DeleteExcelClassifyCheck() {
 	//删除分类
 	if req.ExcelClassifyId > 0 && req.ExcelInfoId == 0 {
 		//判断表格分类下,是否含有表格
-		count, err := data_manage.GetExcelInfoCountByClassifyId(req.ExcelClassifyId)
+		count, err := excel.GetExcelInfoCountByClassifyId(req.ExcelClassifyId)
 		if err != nil {
 			br.Msg = "删除失败"
 			br.ErrMsg = "分类下是否含有指标失败,Err:" + err.Error()
@@ -328,7 +354,7 @@ func (this *ExcelClassifyController) DeleteExcelClassifyCheck() {
 		tipsMsg = "可删除,进行删除操作"
 	}
 
-	resp := response.ExcelClassifyDeleteCheckResp{
+	resp := response2.ExcelClassifyDeleteCheckResp{
 		DeleteStatus: deleteStatus,
 		TipsMsg:      tipsMsg,
 	}
@@ -376,7 +402,7 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 	//删除分类
 	if req.ExcelClassifyId > 0 && req.ExcelInfoId == 0 {
 		//判断是否含有指标
-		count, err := data_manage.GetExcelInfoCountByClassifyId(req.ExcelClassifyId)
+		count, err := excel.GetExcelInfoCountByClassifyId(req.ExcelClassifyId)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "删除失败"
 			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
@@ -389,7 +415,7 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 			return
 		}
 
-		classifyItem, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+		classifyItem, err := excel.GetExcelClassifyById(req.ExcelClassifyId)
 		if err != nil {
 			br.Msg = "删除失败"
 			br.ErrMsg = "获取分类失败,Err:" + err.Error()
@@ -408,10 +434,10 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 		}
 	}
 
-	resp := response.AddExcelInfoResp{}
+	resp := response2.AddExcelInfoResp{}
 	//删除表格
 	if req.ExcelInfoId > 0 {
-		excelInfo, err := data_manage.GetExcelInfoById(req.ExcelInfoId)
+		excelInfo, err := excel.GetExcelInfoById(req.ExcelInfoId)
 		if err != nil {
 			if err.Error() == utils.ErrNoRow() {
 				br.Msg = "表格已删除,请刷新页面"
@@ -428,18 +454,22 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 			br.IsSendEmail = false
 			return
 		}
-		//删除表格及关联指标
-		excelInfo.IsDelete = 1
-		err = excelInfo.Update([]string{"IsDelete"})
+
+		// 删除excel
+		err, errMsg, isSendEmail := excel2.Delete(excelInfo, sysUser)
 		if err != nil {
 			br.Msg = "删除失败"
-			br.ErrMsg = "删除失败,Err:" + err.Error()
+			if errMsg != `` {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			br.IsSendEmail = isSendEmail
 			return
 		}
 
 		// 返回下一个表格的信息
 		{
-			var nextItem *data_manage.ExcelInfo
+			var nextItem *excel.ExcelInfo
 			var condition string
 			var pars []interface{}
 			condition += " AND excel_classify_id=? "
@@ -447,7 +477,7 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 
 			condition += " AND (sort>? OR (sort=? AND excel_info_id<?) ) "
 			pars = append(pars, excelInfo.Sort, excelInfo.Sort, excelInfo.ExcelInfoId)
-			nextItem, err = data_manage.GetNextExcelInfoByCondition(condition, pars)
+			nextItem, err = excel.GetNextExcelInfoByCondition(condition, pars)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "删除失败"
 				br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
@@ -456,37 +486,14 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 
 			// 如果没找到,那么查找下一个分类的第一个表格
 			if nextItem == nil {
-				currClassifyInfo, err := data_manage.GetExcelClassifyById(excelInfo.ExcelClassifyId)
+				currClassifyInfo, err := excel.GetExcelClassifyById(excelInfo.ExcelClassifyId)
 				if err != nil && err.Error() != utils.ErrNoRow() {
 					br.Msg = "删除失败"
 					br.ErrMsg = "获取当前表格分类信息失败,Err:" + err.Error()
 					return
 				}
 
-				//var condition string
-				//var pars []interface{}
-				//condition += " AND level=3 "
-				//pars = append(pars, chartInfo.ExcelClassifyId)
-
-				//condition += " AND (sort>? OR (sort=? and excel_classify_id >?) ) "
-				//pars = append(pars, currClassifyInfo.Sort, currClassifyInfo.Sort, currClassifyInfo.ExcelClassifyId)
-				//
-				//classifyItem, err := data_manage.GetNextExcelClassifyByCondition(condition, pars)
-				//if err != nil && err.Error() != utils.ErrNoRow() {
-				//	br.Msg = "删除失败"
-				//	br.ErrMsg = "获取下一级表格分类信息失败,Err:" + err.Error()
-				//	return
-				//}
-				//if classifyItem != nil {
-				//	nextItem, err = data_manage.GetNextExcelInfo(excelInfo.ExcelClassifyId)
-				//	if err != nil && err.Error() != utils.ErrNoRow() {
-				//		br.Msg = "删除失败"
-				//		br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
-				//		return
-				//	}
-				//}
-
-				nextItem, err = data_manage.GetNextExcelInfo(excelInfo.ExcelClassifyId, currClassifyInfo.Sort)
+				nextItem, err = excel.GetNextExcelInfo(excelInfo.ExcelClassifyId, currClassifyInfo.Sort, currClassifyInfo.Source)
 				if err != nil && err.Error() != utils.ErrNoRow() {
 					br.Msg = "删除失败"
 					br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
@@ -496,7 +503,7 @@ func (this *ExcelClassifyController) DeleteExcelClassify() {
 
 			// 如果找到下一个表格了,那么就返回
 			if nextItem != nil {
-				resp = response.AddExcelInfoResp{
+				resp = response2.AddExcelInfoResp{
 					ExcelInfoId: nextItem.ExcelInfoId,
 					UniqueCode:  nextItem.UniqueCode,
 				}
@@ -561,7 +568,7 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 		return
 	}
 	//判断分类是否存在
-	ExcelClassifyInfo, err := data_manage.GetExcelClassifyById(req.ClassifyId)
+	ExcelClassifyInfo, err := excel.GetExcelClassifyById(req.ClassifyId)
 	if err != nil {
 		br.Msg = "移动失败"
 		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
@@ -571,7 +578,7 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 
 	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
 	if ExcelClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
-		parentExcelClassifyInfo, err := data_manage.GetExcelClassifyById(req.ParentClassifyId)
+		parentExcelClassifyInfo, err := excel.GetExcelClassifyById(req.ParentClassifyId)
 		if err != nil {
 			br.Msg = "移动失败"
 			br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
@@ -586,7 +593,7 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 	//如果有传入 上一个兄弟节点分类id
 	if req.PrevClassifyId > 0 {
 		//上一个兄弟节点
-		prevClassify, err := data_manage.GetExcelClassifyById(req.PrevClassifyId)
+		prevClassify, err := excel.GetExcelClassifyById(req.PrevClassifyId)
 		if err != nil {
 			br.Msg = "移动失败"
 			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
@@ -596,7 +603,7 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 		//如果是移动在两个兄弟节点之间
 		if req.NextClassifyId > 0 {
 			//下一个兄弟节点
-			nextClassify, err := data_manage.GetExcelClassifyById(req.NextClassifyId)
+			nextClassify, err := excel.GetExcelClassifyById(req.NextClassifyId)
 			if err != nil {
 				br.Msg = "移动失败"
 				br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
@@ -606,13 +613,13 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 			if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == ExcelClassifyInfo.Sort {
 				//变更兄弟节点的排序
 				updateSortStr := `sort + 2`
-				_ = data_manage.UpdateExcelClassifySortByParentId(prevClassify.ParentId, prevClassify.ExcelClassifyId, prevClassify.Sort, updateSortStr)
+				_ = excel.UpdateExcelClassifySortByParentId(prevClassify.ParentId, prevClassify.ExcelClassifyId, prevClassify.Sort, updateSortStr)
 			} else {
 				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
 				if nextClassify.Sort-prevClassify.Sort == 1 {
 					//变更兄弟节点的排序
 					updateSortStr := `sort + 1`
-					_ = data_manage.UpdateExcelClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+					_ = excel.UpdateExcelClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
 				}
 			}
 		}
@@ -622,7 +629,7 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 		updateCol = append(updateCol, "Sort", "ModifyTime")
 
 	} else {
-		firstClassify, err := data_manage.GetFirstExcelClassifyByParentId(ExcelClassifyInfo.ParentId)
+		firstClassify, err := excel.GetFirstExcelClassifyByParentId(ExcelClassifyInfo.ParentId)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "移动失败"
 			br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
@@ -632,7 +639,7 @@ func (this *ExcelClassifyController) ExcelClassifyMove() {
 		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
 		if firstClassify != nil && firstClassify.Sort == 0 {
 			updateSortStr := ` sort + 1 `
-			_ = data_manage.UpdateExcelClassifySortByParentId(firstClassify.ParentId, firstClassify.ExcelClassifyId-1, 0, updateSortStr)
+			_ = excel.UpdateExcelClassifySortByParentId(firstClassify.ParentId, firstClassify.ExcelClassifyId-1, 0, updateSortStr)
 		}
 
 		ExcelClassifyInfo.Sort = 0 //那就是排在第一位

+ 334 - 211
controllers/data_manage/excel_info.go → controllers/data_manage/excel/excel_info.go

@@ -1,21 +1,25 @@
-package data_manage
+package excel
 
 import (
+	"archive/zip"
 	"encoding/json"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
-	"eta/eta_api/models/data_manage/request"
-	"eta/eta_api/models/data_manage/response"
+	excel3 "eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
+	"eta/eta_api/models/data_manage/excel/response"
 	"eta/eta_api/services"
-	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
+	excel2 "eta/eta_api/services/data/excel"
 	"eta/eta_api/services/excel"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"github.com/yidane/formula"
+	"io"
 	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
@@ -79,7 +83,7 @@ func (c *ExcelInfoController) Add() {
 		return
 	}
 
-	excelClassify, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+	excelClassify, err := excel3.GetExcelClassifyById(req.ExcelClassifyId)
 	if err != nil {
 		if err.Error() == utils.ErrNoRow() {
 			br.Msg = "分类不存在"
@@ -107,7 +111,7 @@ func (c *ExcelInfoController) Add() {
 	pars = append(pars, req.ExcelName)
 
 	// 获取分类下是否存在该表格名称
-	count, err := data_manage.GetExcelInfoCountByCondition(condition, pars)
+	count, err := excel3.GetExcelInfoCountByCondition(condition, pars)
 	if err != nil {
 		br.Msg = "判断表格名称是否存在失败"
 		br.ErrMsg = "判断表格名称是否存在失败,Err:" + err.Error()
@@ -124,6 +128,9 @@ func (c *ExcelInfoController) Add() {
 		req.Source = 1
 	}
 	content := req.Content
+
+	// 引用的指标id
+	edbInfoIdList := make([]int, 0)
 	// 自定义表格
 	if req.Source == 2 {
 		jsonStrByte, err := json.Marshal(req.TableData)
@@ -140,12 +147,14 @@ func (c *ExcelInfoController) Add() {
 			return
 		}
 
-		tableDataConfig, err := data.GetTableDataConfig(tableData)
+		tableDataConfig, err := excel2.GetTableDataConfig(tableData)
 		if err != nil {
 			br.Msg = "自定义表格数据获取失败"
 			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
 			return
 		}
+		edbInfoIdList = tableDataConfig.EdbInfoIdList
+
 		contentByte, err := json.Marshal(tableDataConfig)
 		if err != nil {
 			br.Msg = "自定义表格数据获取失败"
@@ -164,10 +173,35 @@ func (c *ExcelInfoController) Add() {
 			return
 		}
 		content = string(contentByte)
+
+		var result request.MixedTableReq
+		err = json.Unmarshal(contentByte, &result)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
+			return
+		}
+		newResult, tmpErr := excel2.GetMixedTableCellData(result.Data)
+		if tmpErr != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
+			return
+		}
+		edbInfoIdMap := make(map[int]int)
+		for _, tmpV := range newResult {
+			for _, v := range tmpV {
+				if v.EdbInfoId > 0 {
+					if _, ok := edbInfoIdMap[v.EdbInfoId]; !ok {
+						edbInfoIdMap[v.EdbInfoId] = v.EdbInfoId
+						edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+					}
+				}
+			}
+		}
 	}
 
 	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
-	excelInfo := &data_manage.ExcelInfo{
+	excelInfo := &excel3.ExcelInfo{
 		//ExcelInfoId:     0,
 		ExcelName:       req.ExcelName,
 		Source:          req.Source,
@@ -184,7 +218,20 @@ func (c *ExcelInfoController) Add() {
 		CreateTime:      time.Now(),
 	}
 
-	err = data_manage.AddExcelInfo(excelInfo)
+	excelEdbMappingList := make([]*excel3.ExcelEdbMapping, 0)
+	if len(edbInfoIdList) > 0 {
+		for _, edbInfoId := range edbInfoIdList {
+			excelEdbMappingList = append(excelEdbMappingList, &excel3.ExcelEdbMapping{
+				//ExcelEdbMappingId: 0,
+				//ExcelInfoId:       0,
+				Source:     excelInfo.Source,
+				EdbInfoId:  edbInfoId,
+				CreateTime: time.Now(),
+				ModifyTime: time.Now(),
+			})
+		}
+	}
+	err = excel3.AddExcelInfo(excelInfo, excelEdbMappingList)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存失败,Err:" + err.Error()
@@ -193,9 +240,14 @@ func (c *ExcelInfoController) Add() {
 
 	// 更新excel下载地址(默认的EXCEL需要更新,自定义表格不需要更新)
 	if req.Source == 1 {
-		go UpdateExcelInfoFileUrl(excelInfo)
+		go excel2.UpdateExcelInfoFileUrl(excelInfo)
+	}
+	_, err = services.UpdateExcelEditMark(excelInfo.ExcelInfoId, sysUser.AdminId, 1, sysUser.RealName)
+	if err != nil {
+		br.Msg = "查询标记状态失败"
+		br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+		return
 	}
-
 	resp := new(response.AddExcelInfoResp)
 	resp.ExcelInfoId = excelInfo.ExcelInfoId
 	resp.UniqueCode = excelInfo.UniqueCode
@@ -233,6 +285,8 @@ func (c *ExcelInfoController) Add() {
 // @Param   ExcelClassifyId   query   int  true       "分类id"
 // @Param   Keyword   query   string  true       "搜索关键词"
 // @Param   AdminId   query   int  false       "创建人id"
+// @Param   Source   query   int  true       "格来源,1:excel插件的表格,2:自定义表格,3:混合表格,默认:1"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
 // @Success 200 {object} response.ExcelListResp
 // @router /excel_info/list [get]
 func (c *ExcelInfoController) List() {
@@ -255,6 +309,7 @@ func (c *ExcelInfoController) List() {
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyword := c.GetString("Keyword")
 	adminId, _ := c.GetInt("AdminId")
+	source, _ := c.GetInt("Source")
 
 	var total int
 	page := paging.GetPaging(currentIndex, pageSize, total)
@@ -271,9 +326,18 @@ func (c *ExcelInfoController) List() {
 	var condition string
 	var pars []interface{}
 
+	// 如果没有传的话,那就是代表前三种表格
+	if source <= 0 {
+		condition += " AND source in (?,?,?) "
+		pars = append(pars, utils.EXCEL_DEFAULT, utils.TIME_TABLE, utils.MIXED_TABLE)
+	} else {
+		condition += " AND source = ? "
+		pars = append(pars, source)
+	}
+
 	// 筛选分类
 	if excelClassifyId > 0 {
-		_, err := data_manage.GetExcelClassifyById(excelClassifyId)
+		_, err := excel3.GetExcelClassifyById(excelClassifyId)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "获取表格信息失败"
 			br.ErrMsg = "获取信息失败,GetExcelClassify,Err:" + err.Error()
@@ -290,8 +354,14 @@ func (c *ExcelInfoController) List() {
 		condition += " AND sys_user_id = ? "
 		pars = append(pars, adminId)
 	}
+	//只看我的
+	isShowMe, _ := c.GetBool("IsShowMe")
+	if isShowMe {
+		condition += " AND sys_user_id = ? "
+		pars = append(pars, sysUser.AdminId)
+	}
 	//获取表格信息
-	list, err := data_manage.GetNoContentExcelListByCondition(condition, pars, startSize, pageSize)
+	list, err := excel3.GetNoContentExcelListByCondition(condition, pars, startSize, pageSize)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Success = true
 		br.Msg = "获取表格信息失败"
@@ -300,10 +370,10 @@ func (c *ExcelInfoController) List() {
 	}
 
 	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
-		list = make([]*data_manage.MyExcelInfoList, 0)
+		list = make([]*excel3.MyExcelInfoList, 0)
 	}
 	// 总数据量
-	dataCount, err := data_manage.GetExcelListCountByCondition(condition, pars)
+	dataCount, err := excel3.GetExcelListCountByCondition(condition, pars)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取表格列表信息失败"
 		br.ErrMsg = "获取表格列表数据总数失败,Err:" + err.Error()
@@ -350,15 +420,28 @@ func (c *ExcelInfoController) Detail() {
 	}
 
 	// 获取数据详情
-	excelDetail, errMsg, err := data.GetExcelDetailInfoByExcelInfoId(excelInfoId)
+	excelDetail, errMsg, err := excel2.GetExcelDetailInfoByExcelInfoId(excelInfoId)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()
 		return
 	}
 
+	// 编辑状态
+	markStatus, err := services.UpdateExcelEditMark(excelInfoId, sysUser.AdminId, 2, sysUser.RealName)
+	if err != nil {
+		br.Msg = "查询标记状态失败"
+		br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+		return
+	}
+	if markStatus.Status == 0 {
+		excelDetail.CanEdit = true
+	} else {
+		excelDetail.Editor = markStatus.Editor
+	}
+
 	// excel表格按钮权限
-	excelDetail.Button = data.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source)
+	excelDetail.Button = excel2.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source)
 
 	br.Ret = 200
 	br.Success = true
@@ -424,7 +507,15 @@ func (c *ExcelInfoController) Edit() {
 		return
 	}
 
-	excelClassify, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+	// 标记编辑状态
+	_, err = services.UpdateExcelEditMark(req.ExcelInfoId, sysUser.AdminId, 1, sysUser.RealName)
+	if err != nil {
+		br.Msg = "查询标记状态失败"
+		br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+		return
+	}
+
+	excelClassify, err := excel3.GetExcelClassifyById(req.ExcelClassifyId)
 	if err != nil {
 		if err.Error() == utils.ErrNoRow() {
 			br.Msg = "分类不存在"
@@ -456,7 +547,7 @@ func (c *ExcelInfoController) Edit() {
 	pars = append(pars, req.ExcelName)
 
 	// 获取分类下是否存在该表格名称
-	count, err := data_manage.GetExcelInfoCountByCondition(condition, pars)
+	count, err := excel3.GetExcelInfoCountByCondition(condition, pars)
 	if err != nil {
 		br.Msg = "ETA判断表格名称是否存在失败"
 		br.ErrMsg = "判断ETA表格名称是否存在失败,Err:" + err.Error()
@@ -468,7 +559,7 @@ func (c *ExcelInfoController) Edit() {
 		return
 	}
 
-	excelInfo, err := data_manage.GetExcelInfoById(req.ExcelInfoId)
+	excelInfo, err := excel3.GetExcelInfoById(req.ExcelInfoId)
 	if err != nil {
 		br.Msg = "获取ETA表格失败"
 		br.ErrMsg = "获取ETA表格失败,Err:" + err.Error()
@@ -477,7 +568,7 @@ func (c *ExcelInfoController) Edit() {
 
 	// 操作权限校验
 	{
-		button := data.GetExcelInfoOpButton(sysUser, excelInfo.SysUserId, excelInfo.Source)
+		button := excel2.GetExcelInfoOpButton(sysUser, excelInfo.SysUserId, excelInfo.Source)
 		if !button.OpButton {
 			br.Msg = "无操作权限"
 			br.Msg = "无操作权限"
@@ -486,6 +577,8 @@ func (c *ExcelInfoController) Edit() {
 		}
 	}
 
+	// 引用的指标id
+	edbInfoIdList := make([]int, 0)
 	content := req.Content
 	switch excelInfo.Source {
 	case 2: // 自定义表格
@@ -502,7 +595,8 @@ func (c *ExcelInfoController) Edit() {
 			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
 			return
 		}
-		tableDataConfig, err := data.GetTableDataConfig(tableData)
+		edbInfoIdList = tableData.EdbInfoIdList
+		tableDataConfig, err := excel2.GetTableDataConfig(tableData)
 		if err != nil {
 			br.Msg = "自定义表格数据获取失败"
 			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
@@ -523,6 +617,31 @@ func (c *ExcelInfoController) Edit() {
 			return
 		}
 		content = string(contentByte)
+
+		var result request.MixedTableReq
+		err = json.Unmarshal(contentByte, &result)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
+			return
+		}
+		newResult, tmpErr := excel2.GetMixedTableCellData(result.Data)
+		if tmpErr != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
+			return
+		}
+		edbInfoIdMap := make(map[int]int)
+		for _, tmpV := range newResult {
+			for _, v := range tmpV {
+				if v.EdbInfoId > 0 {
+					if _, ok := edbInfoIdMap[v.EdbInfoId]; !ok {
+						edbInfoIdMap[v.EdbInfoId] = v.EdbInfoId
+						edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+					}
+				}
+			}
+		}
 	}
 
 	excelInfo.ModifyTime = time.Now()
@@ -532,9 +651,29 @@ func (c *ExcelInfoController) Edit() {
 	excelInfo.ExcelImage = req.ExcelImage
 	excelInfo.Content = content
 
-	updateExcelInfoParams := []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "ExcelImage", "Content"}
+	// 自动保存时不会传缩略图,也就不更新这个字段
+	var updateExcelInfoParams []string
+	if req.ExcelImage != "" {
+		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "ExcelImage", "Content"}
+	} else {
+		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "Content"}
+	}
+
+	excelEdbMappingList := make([]*excel3.ExcelEdbMapping, 0)
+	if len(edbInfoIdList) > 0 {
+		for _, edbInfoId := range edbInfoIdList {
+			excelEdbMappingList = append(excelEdbMappingList, &excel3.ExcelEdbMapping{
+				//ExcelEdbMappingId: 0,
+				ExcelInfoId: excelInfo.ExcelInfoId,
+				Source:      excelInfo.Source,
+				EdbInfoId:   edbInfoId,
+				CreateTime:  time.Now(),
+				ModifyTime:  time.Now(),
+			})
+		}
+	}
 
-	err = data_manage.EditExcelInfo(excelInfo, updateExcelInfoParams)
+	err = excel3.EditExcelInfo(excelInfo, updateExcelInfoParams, excelEdbMappingList)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存失败,Err:" + err.Error()
@@ -543,12 +682,12 @@ func (c *ExcelInfoController) Edit() {
 
 	// 更新excel下载地址(默认的EXCEL需要更新,自定义表格不需要更新)
 	if excelInfo.Source == 1 {
-		go UpdateExcelInfoFileUrl(excelInfo)
+		go excel2.UpdateExcelInfoFileUrl(excelInfo)
 	}
 
 	// 加入草稿
 	{
-		excelDraftInfo := &data_manage.ExcelDraft{
+		excelDraftInfo := &excel3.ExcelDraft{
 			//ExcelDraftId: 0,
 			ExcelId:    req.ExcelInfoId,
 			Name:       req.ExcelName,
@@ -558,7 +697,7 @@ func (c *ExcelInfoController) Edit() {
 			CreateTime: time.Now(),
 		}
 
-		err = data_manage.AddExcelDraft(excelDraftInfo)
+		err = excel3.AddExcelDraft(excelDraftInfo)
 	}
 
 	resp := response.AddExcelInfoResp{
@@ -619,7 +758,7 @@ func (c *ExcelInfoController) Move() {
 	}
 
 	//判断分类是否存在
-	_, err = data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+	_, err = excel3.GetExcelClassifyById(req.ExcelClassifyId)
 	if err != nil {
 		br.Msg = "移动失败"
 		br.ErrMsg = "获取分类信息失败" + err.Error()
@@ -633,7 +772,7 @@ func (c *ExcelInfoController) Move() {
 	}
 
 	// 获取表格信息
-	excelInfo, err := data_manage.GetExcelInfoById(req.ExcelInfoId)
+	excelInfo, err := excel3.GetExcelInfoById(req.ExcelInfoId)
 	if err != nil {
 		br.Msg = "移动失败"
 		br.ErrMsg = "获取表格信息失败,Err:" + err.Error()
@@ -643,7 +782,7 @@ func (c *ExcelInfoController) Move() {
 	//如果改变了分类,那么移动该表格数据
 	if excelInfo.ExcelClassifyId != req.ExcelClassifyId {
 		//查询需要修改的分类下是否存在同一个表格名称
-		tmpExcelInfo, tmpErr := data_manage.GetExcelInfoByClassifyIdAndName(req.ExcelClassifyId, excelInfo.ExcelName)
+		tmpExcelInfo, tmpErr := excel3.GetExcelInfoByClassifyIdAndName(req.ExcelClassifyId, excelInfo.ExcelName)
 		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
 			br.Msg = "移动失败"
 			br.ErrMsg = "移动失败,Err:" + tmpErr.Error()
@@ -670,7 +809,7 @@ func (c *ExcelInfoController) Move() {
 	updateCol := make([]string, 0)
 	//如果有传入 上一个兄弟节点分类id
 	if req.PrevExcelInfoId > 0 {
-		prevExcelInfo, err := data_manage.GetExcelInfoById(req.PrevExcelInfoId)
+		prevExcelInfo, err := excel3.GetExcelInfoById(req.PrevExcelInfoId)
 		if err != nil {
 			br.Msg = "移动失败"
 			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
@@ -680,7 +819,7 @@ func (c *ExcelInfoController) Move() {
 		//如果是移动在两个兄弟节点之间
 		if req.NextExcelInfoId > 0 {
 			//下一个兄弟节点
-			nextExcelInfo, err := data_manage.GetExcelInfoById(req.NextExcelInfoId)
+			nextExcelInfo, err := excel3.GetExcelInfoById(req.NextExcelInfoId)
 			if err != nil {
 				br.Msg = "移动失败"
 				br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
@@ -690,13 +829,13 @@ func (c *ExcelInfoController) Move() {
 			if prevExcelInfo.Sort == nextExcelInfo.Sort || prevExcelInfo.Sort == excelInfo.Sort {
 				//变更兄弟节点的排序
 				updateSortStr := `sort + 2`
-				_ = data_manage.UpdateExcelInfoSortByClassifyId(prevExcelInfo.ExcelClassifyId, prevExcelInfo.Sort, prevExcelInfo.ExcelInfoId, updateSortStr)
+				_ = excel3.UpdateExcelInfoSortByClassifyId(prevExcelInfo.ExcelClassifyId, prevExcelInfo.Sort, prevExcelInfo.ExcelInfoId, updateSortStr)
 			} else {
 				//如果下一个兄弟的排序权重正好是上个兄弟节点下一层,那么需要再加一层了
 				if nextExcelInfo.Sort-prevExcelInfo.Sort == 1 {
 					//变更兄弟节点的排序
 					updateSortStr := `sort + 1`
-					_ = data_manage.UpdateExcelInfoSortByClassifyId(prevExcelInfo.ExcelClassifyId, prevExcelInfo.Sort, prevExcelInfo.ExcelInfoId, updateSortStr)
+					_ = excel3.UpdateExcelInfoSortByClassifyId(prevExcelInfo.ExcelClassifyId, prevExcelInfo.Sort, prevExcelInfo.ExcelInfoId, updateSortStr)
 				}
 			}
 		}
@@ -707,7 +846,7 @@ func (c *ExcelInfoController) Move() {
 		updateCol = append(updateCol, "Sort", "ModifyTime")
 
 	} else {
-		firstClassify, err := data_manage.GetFirstExcelInfoByClassifyId(req.ExcelClassifyId)
+		firstClassify, err := excel3.GetFirstExcelInfoByClassifyId(req.ExcelClassifyId)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "移动失败"
 			br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
@@ -717,7 +856,7 @@ func (c *ExcelInfoController) Move() {
 		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
 		if firstClassify != nil && firstClassify.Sort == 0 {
 			updateSortStr := ` sort + 1 `
-			_ = data_manage.UpdateExcelInfoSortByClassifyId(firstClassify.ExcelClassifyId, 0, firstClassify.ExcelInfoId-1, updateSortStr)
+			_ = excel3.UpdateExcelInfoSortByClassifyId(firstClassify.ExcelClassifyId, 0, firstClassify.ExcelInfoId-1, updateSortStr)
 		}
 
 		// 变更表格在当前分类下的排序
@@ -803,36 +942,28 @@ func (c *ExcelInfoController) Delete() {
 	}
 
 	// 获取表格信息
-	excelInfo, err := data_manage.GetExcelInfoById(req.ExcelInfoId)
+	excelInfo, err := excel3.GetExcelInfoById(req.ExcelInfoId)
 	if err != nil {
 		br.Msg = "删除失败"
 		br.ErrMsg = "获取表格信息失败,Err:" + err.Error()
 		return
 	}
 
-	// 操作权限校验
-	{
-		button := data.GetExcelInfoOpButton(sysUser, excelInfo.SysUserId, excelInfo.Source)
-		if !button.DeleteButton {
-			br.Msg = "无操作权限"
-			br.Msg = "无操作权限"
-			br.IsSendEmail = false
-			return
-		}
-	}
-
-	//更新
-	excelInfo.IsDelete = 1
-	err = excelInfo.Update([]string{"IsDelete"})
+	// 删除excel
+	err, errMsg, isSendEmail := excel2.Delete(excelInfo, sysUser)
 	if err != nil {
 		br.Msg = "删除失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
 		br.ErrMsg = "删除失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
 		return
 	}
 
 	// 返回下一个表格的信息
 	{
-		var nextItem *data_manage.ExcelInfo
+		var nextItem *excel3.ExcelInfo
 		var condition string
 		var pars []interface{}
 		condition += " AND excel_classify_id=? "
@@ -840,7 +971,7 @@ func (c *ExcelInfoController) Delete() {
 
 		condition += " AND sort>=? "
 		pars = append(pars, excelInfo.Sort)
-		nextItem, err = data_manage.GetNextExcelInfoByCondition(condition, pars)
+		nextItem, err = excel3.GetNextExcelInfoByCondition(condition, pars)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "删除失败"
 			br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
@@ -849,7 +980,7 @@ func (c *ExcelInfoController) Delete() {
 
 		// 如果没找到,那么查找下一个分类的第一个表格
 		if nextItem == nil {
-			currClassifyInfo, err := data_manage.GetExcelClassifyById(excelInfo.ExcelClassifyId)
+			currClassifyInfo, err := excel3.GetExcelClassifyById(excelInfo.ExcelClassifyId)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "删除失败"
 				br.ErrMsg = "获取当前表格分类信息失败,Err:" + err.Error()
@@ -878,7 +1009,7 @@ func (c *ExcelInfoController) Delete() {
 			//	}
 			//}
 
-			nextItem, err = data_manage.GetNextExcelInfo(excelInfo.ExcelClassifyId, currClassifyInfo.Sort)
+			nextItem, err = excel3.GetNextExcelInfo(excelInfo.ExcelClassifyId, currClassifyInfo.Sort, currClassifyInfo.Source)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "删除失败"
 				br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
@@ -947,7 +1078,7 @@ func (c *ExcelInfoController) AddDraft() {
 		return
 	}
 
-	excelClassify, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
+	excelClassify, err := excel3.GetExcelClassifyById(req.ExcelClassifyId)
 	if err != nil {
 		if err.Error() == utils.ErrNoRow() {
 			br.Msg = "分类不存在"
@@ -975,14 +1106,14 @@ func (c *ExcelInfoController) AddDraft() {
 	pars = append(pars, req.ExcelName)
 
 	// 获取分类下是否存在该表格名称
-	_, err = data_manage.GetExcelInfoById(req.ExcelInfoId)
+	_, err = excel3.GetExcelInfoById(req.ExcelInfoId)
 	if err != nil {
 		br.Msg = "判断表格名称是否存在失败"
 		br.ErrMsg = "判断表格名称是否存在失败,Err:" + err.Error()
 		return
 	}
 
-	excelDraftInfo := &data_manage.ExcelDraft{
+	excelDraftInfo := &excel3.ExcelDraft{
 		//ExcelDraftId: 0,
 		ExcelId:    req.ExcelInfoId,
 		Name:       req.ExcelName,
@@ -992,13 +1123,20 @@ func (c *ExcelInfoController) AddDraft() {
 		CreateTime: time.Now(),
 	}
 
-	err = data_manage.AddExcelDraft(excelDraftInfo)
+	err = excel3.AddExcelDraft(excelDraftInfo)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
 
+	_, err = services.UpdateExcelEditMark(req.ExcelInfoId, sysUser.AdminId, 1, sysUser.RealName)
+	if err != nil {
+		br.Msg = "查询标记状态失败"
+		br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+		return
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "添加成功"
@@ -1033,7 +1171,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 		return
 	}
 	//获取eta表格信息
-	excelInfo, err := data_manage.GetExcelInfoByUniqueCode(uniqueCode)
+	excelInfo, err := excel3.GetExcelInfoByUniqueCode(uniqueCode)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取ETA表格信息失败,Err:" + err.Error()
@@ -1054,21 +1192,21 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "获取excel数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = luckySheetData.GetTableDataByLuckySheetDataStr()
+		tableData, err = luckySheetData.GetTableDataByLuckySheetDataStr(true)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
 			return
 		}
 	case 2:
-		var tableDataConfig data.TableDataConfig
+		var tableDataConfig excel2.TableDataConfig
 		err = json.Unmarshal([]byte(excelInfo.Content), &tableDataConfig)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		result, err := data.GetDataByTableDataConfig(tableDataConfig)
+		result, err := excel2.GetDataByTableDataConfig(tableDataConfig)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取最新的表格数据失败,Err:" + err.Error()
@@ -1088,7 +1226,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		newResult, tmpErr := data.GetMixedTableCellData(result.Data)
+		newResult, tmpErr := excel2.GetMixedTableCellData(result.Data)
 		if tmpErr != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
@@ -1117,47 +1255,6 @@ func (c *ExcelInfoController) GetExcelTableData() {
 	br.Data = resp
 }
 
-// UpdateExcelInfoFileUrl 更新excel表格的下载地址
-func UpdateExcelInfoFileUrl(excelInfo *data_manage.ExcelInfo) {
-	var err error
-	defer func() {
-		if err != nil {
-			go alarm_msg.SendAlarmMsg(fmt.Sprintf("更新excel表格的下载地址失败,表格id:%d;表格名称:%s; ERR:%s", excelInfo.ExcelInfoId, excelInfo.ExcelName, err), 3)
-		}
-	}()
-	fileName := excelInfo.ExcelName + "_" + excelInfo.UniqueCode + ".xlsx"
-	luckySheetData, err := excel.GetLuckySheetData(excelInfo.Content)
-	if err != nil {
-		fmt.Println("err:", err)
-		return
-	}
-	//_, err = luckySheetData.GetTableDataByLuckySheetDataStr()
-	downloadFilePath, err := luckySheetData.ToExcel()
-	defer func() {
-		_ = os.Remove(downloadFilePath)
-	}()
-	if err != nil {
-		fmt.Println("err:", err)
-		return
-	}
-
-	resourceUrl := ``
-	//上传到阿里云 和 minio
-	if utils.ObjectStorageClient == "minio" {
-		resourceUrl, err = services.UploadImgToMinIo(fileName, downloadFilePath)
-		if err != nil {
-			return
-		}
-	} else {
-		resourceUrl, err = services.UploadAliyunV2(fileName, downloadFilePath)
-		if err != nil {
-			return
-		}
-	}
-	excelInfo.FileUrl = resourceUrl
-	err = excelInfo.Update([]string{"FileUrl"})
-}
-
 //func init() {
 //
 //	excelInfo, err := data_manage.GetExcelInfoById(52)
@@ -1317,7 +1414,7 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 		return
 	}
 
-	dataList, err := data.GetFirstEdbDataList(edbInfo, num, []string{})
+	dataList, err := excel2.GetFirstEdbDataList(edbInfo, num, []string{})
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1385,7 +1482,7 @@ func (c *ExcelInfoController) GetOtherEdbData() {
 		br.ErrMsg = fmt.Sprint("获取指标信息失败,Err:", err.Error())
 		return
 	}
-	dataList, err := data.GetOtherEdbDataList(edbInfo, req.DateList)
+	dataList, err := excel2.GetOtherEdbDataList(edbInfo, req.DateList)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1559,7 +1656,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 				return
 			}
 
-			dataList, err := data.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
+			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
 			if err != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1642,7 +1739,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 			br.ErrMsg = "获取指标信息失败,err:" + err.Error()
 			return
 		}
-		firstDataList, err := data.GetFirstHistoryEdbDataList(tmpEdbInfo, req.Num, req.EndDate)
+		firstDataList, err := excel2.GetFirstHistoryEdbDataList(tmpEdbInfo, req.Num, req.EndDate)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1671,7 +1768,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 				return
 			}
 
-			dataList, err := data.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
+			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
 			if err != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1719,7 +1816,7 @@ func (c *ExcelInfoController) Refresh() {
 	}
 
 	// 获取数据详情
-	excelDetail, errMsg, err := data.GetExcelDetailInfoByExcelInfoId(excelInfoId)
+	excelDetail, errMsg, err := excel2.GetExcelDetailInfoByExcelInfoId(excelInfoId)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()
@@ -1728,7 +1825,7 @@ func (c *ExcelInfoController) Refresh() {
 
 	// 操作权限校验
 	{
-		button := data.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source)
+		button := excel2.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source)
 		if !button.RefreshButton {
 			br.Msg = "无操作权限"
 			br.Msg = "无操作权限"
@@ -1797,13 +1894,13 @@ func (c *ExcelInfoController) Download() {
 		br.IsSendEmail = false
 		return
 	}
-	var excelInfo *data_manage.ExcelInfo
+	var excelInfo *excel3.ExcelInfo
 	var err error
 	//获取eta表格信息
 	if excelInfoId > 0 {
-		excelInfo, err = data_manage.GetExcelInfoById(excelInfoId)
+		excelInfo, err = excel3.GetExcelInfoById(excelInfoId)
 	} else {
-		excelInfo, err = data_manage.GetExcelInfoByUniqueCode(uniqueCode)
+		excelInfo, err = excel3.GetExcelInfoByUniqueCode(uniqueCode)
 	}
 	if err != nil {
 		br.Msg = "获取失败"
@@ -1827,14 +1924,14 @@ func (c *ExcelInfoController) Download() {
 	case 1:
 		br.Msg = "表格类型异常"
 	case 2: // 自定义表格
-		var tableDataConfig data.TableDataConfig
+		var tableDataConfig excel2.TableDataConfig
 		err = json.Unmarshal([]byte(excelInfo.Content), &tableDataConfig)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		result, err := data.GetDataByTableDataConfig(tableDataConfig)
+		result, err := excel2.GetDataByTableDataConfig(tableDataConfig)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取最新的表格数据失败,Err:" + err.Error()
@@ -1854,7 +1951,7 @@ func (c *ExcelInfoController) Download() {
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		newResult, tmpErr := data.GetMixedTableCellData(result.Data)
+		newResult, tmpErr := excel2.GetMixedTableCellData(result.Data)
 		if tmpErr != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
@@ -1906,7 +2003,6 @@ func (c *ExcelInfoController) Copy() {
 		return
 	}
 
-	deleteCache := true
 	var req request.CopyExcelInfoReq
 	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -1920,17 +2016,15 @@ func (c *ExcelInfoController) Copy() {
 	}
 
 	cacheKey := "CACHE_TABLE_INFO_EDIT_" + strconv.Itoa(req.ExcelInfoId)
-	defer func() {
-		if deleteCache {
-			_ = utils.Rc.Delete(cacheKey)
-		}
-	}()
 	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
-		deleteCache = false
 		br.Msg = "系统处理中,请稍后重试!"
 		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(c.Ctx.Input.RequestBody)
 		return
 	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
 	req.ExcelName = strings.Trim(req.ExcelName, " ")
 	if req.ExcelName == "" {
 		br.Msg = "请填写表格名称!"
@@ -1944,97 +2038,17 @@ func (c *ExcelInfoController) Copy() {
 		return
 	}
 
-	excelClassify, err := data_manage.GetExcelClassifyById(req.ExcelClassifyId)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "分类不存在"
-			br.ErrMsg = "分类不存在"
-			br.IsSendEmail = false
-			return
-		}
-		br.Msg = "获取分类信息失败"
-		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
-		return
-	}
-	if excelClassify == nil {
-		br.Msg = "分类不存在"
-		br.ErrMsg = "分类不存在"
-		br.IsSendEmail = false
-		return
-	}
-
-	// 获取原ETA表格信息
-	oldExcelInfo, err := data_manage.GetExcelInfoById(req.ExcelInfoId)
+	excelInfo, err, errMsg, isSendEmail := excel2.Copy(req.ExcelInfoId, req.ExcelClassifyId, req.ExcelName, sysUser)
 	if err != nil {
-		br.Msg = "获取ETA表格失败"
-		br.ErrMsg = "获取ETA表格失败,Err:" + err.Error()
-		return
-	}
-
-	// 操作权限校验
-	{
-		button := data.GetExcelInfoOpButton(sysUser, oldExcelInfo.SysUserId, oldExcelInfo.Source)
-		if !button.CopyButton {
-			br.Msg = "无操作权限"
-			br.Msg = "无操作权限"
-			br.IsSendEmail = false
-			return
+		br.Msg = "复制失败"
+		if errMsg != `` {
+			br.Msg = errMsg
 		}
-	}
-
-	// 检验分类下是否存在该表格名称
-	{
-		var condition string
-		var pars []interface{}
-		condition += " AND excel_classify_id=? "
-		pars = append(pars, req.ExcelClassifyId)
-
-		condition += " AND excel_name=? "
-		pars = append(pars, req.ExcelName)
-
-		count, err := data_manage.GetExcelInfoCountByCondition(condition, pars)
-		if err != nil {
-			br.Msg = "判断表格名称是否存在失败"
-			br.ErrMsg = "判断表格名称是否存在失败,Err:" + err.Error()
-			return
-		}
-		if count > 0 {
-			br.Msg = "表格名称已存在,请重新填写表格名称"
-			br.IsSendEmail = false
-			return
-		}
-	}
-
-	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
-	excelInfo := &data_manage.ExcelInfo{
-		//ExcelInfoId:     0,
-		ExcelName:       req.ExcelName,
-		Source:          oldExcelInfo.Source,
-		ExcelType:       oldExcelInfo.ExcelType,
-		UniqueCode:      utils.MD5(utils.EXCEL_DATA_PREFIX + "_" + timestamp),
-		ExcelClassifyId: req.ExcelClassifyId,
-		SysUserId:       sysUser.AdminId,
-		SysUserRealName: sysUser.RealName,
-		Content:         oldExcelInfo.Content,
-		ExcelImage:      oldExcelInfo.ExcelImage,
-		Sort:            0,
-		IsDelete:        0,
-		ModifyTime:      time.Now(),
-		CreateTime:      time.Now(),
-	}
-
-	err = data_manage.AddExcelInfo(excelInfo)
-	if err != nil {
-		br.Msg = "保存失败"
-		br.ErrMsg = "保存失败,Err:" + err.Error()
+		br.ErrMsg = "复制失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
 		return
 	}
 
-	// 更新excel下载地址(默认的EXCEL需要更新,自定义表格不需要更新)
-	if excelInfo.Source == 1 {
-		go UpdateExcelInfoFileUrl(excelInfo)
-	}
-
 	resp := new(response.AddExcelInfoResp)
 	resp.ExcelInfoId = excelInfo.ExcelInfoId
 	resp.UniqueCode = excelInfo.UniqueCode
@@ -2109,3 +2123,112 @@ func (c *ExcelInfoController) Copy() {
 //	//byteStr, _ := json.Marshal(result)
 //	//utils.FileLog.Info(string(byteStr))
 //}
+
+//func init() {
+//	startNow := time.Now()
+//	fmt.Println(startNow.Format(utils.FormatDateTime))
+//	startTime := startNow.Unix()
+//	//filePath := `static/tmpFile/test/化工23.05.24.xlsx`
+//	filePath := `static/tmpFile/1. TAROUND_202308010201-500.xlsx`
+//	//filePath := `static/tmpFile/化工23.05.24.xml`
+//	//filePath := `static/tmpFile/化工23.05.24/xl/workbook.xml`
+//	//filePath := `static/tmpFile/1. TAROUND_202308010201.xlsx`
+//
+//	//filePath := `static/tmpFile/1. TAROUND_202308010201-2.xlsx`
+//
+//	//filePath := `static/tmpFile/5. Outage_Values_Daily.xlsx`
+//	//filePath := `static/tmpFile/1. TAROUND_202308010201 - 副本.xlsx`
+//	excel.ConvToLuckySheet(filePath)
+//	cz := time.Now().Unix() - startTime
+//	fmt.Printf("处理完需要的时间:%d秒;约耗时:%d分%d秒", cz, cz/60, cz%60)
+//	fmt.Println()
+//
+//	//sheetName := `日度`
+//	//sheetName := `月度`
+//	//fmt.Println(excel.GetDefaultSheetIndex(filePath, sheetName))
+//
+//	//decompressZip(filePath)
+//}
+
+// src: 需要解压的zip文件
+func decompressZip(src string) error {
+	archive, err := zip.OpenReader(src)
+	if err != nil {
+		return err
+	}
+	dir := filepath.Dir(src) + "/zip"
+	defer archive.Close()
+	for _, f := range archive.File {
+		filePath := filepath.Join(dir, f.Name)
+		if f.FileInfo().IsDir() {
+			os.MkdirAll(filePath, os.ModePerm)
+			continue
+		}
+		if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
+			return fmt.Errorf("failed to make directory (%v)", err)
+		}
+		dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+		if err != nil {
+			return fmt.Errorf("failed to create file (%v)", err)
+		}
+		fileInArchive, err := f.Open()
+		if err != nil {
+			return fmt.Errorf("failed to open file in zip (%v)", err)
+		}
+		if _, err := io.Copy(dstFile, fileInArchive); err != nil {
+			return fmt.Errorf("failed to copy file in zip (%v)", err)
+		}
+		dstFile.Close()
+		fileInArchive.Close()
+	}
+	return nil
+}
+
+// MarkEditStatus
+// @Title 标记沙盘编辑状态
+// @Description 标记沙盘编辑状态接口
+// @Param	request	body request.MarkEditSandbox true "type json string"
+// @Success 200 标记成功 ;202 标记成功
+// @router /excel_info/mark [post]
+func (this *ExcelInfoController) MarkEditStatus() {
+	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 request.MarkEditExcel
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ExcelInfoId <= 0 {
+		br.Msg = "缺少表格编号"
+		return
+	}
+	if req.Status <= 0 {
+		br.Msg = "标记状态异常"
+		return
+	}
+	//更新标记key
+	data, err := services.UpdateExcelEditMark(req.ExcelInfoId, sysUser.AdminId, req.Status, sysUser.RealName)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "标记成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+	br.Data = data
+}

+ 172 - 0
controllers/data_manage/jiayue_edb_source.go

@@ -0,0 +1,172 @@
+package data_manage
+
+import (
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// JiaYueEdbSourceController 嘉悦数据源
+type JiaYueEdbSourceController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 数据源列表
+// @Description 数据源列表
+// @Success 200 string "获取成功"
+// @router /edb_source/jiayue/list [get]
+func (this *JiaYueEdbSourceController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+
+	cond := ` AND is_base = 1 AND from_bridge = 1`
+	pars := make([]interface{}, 0)
+	sources, e := data_manage.GetEdbSourceItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标来源失败, Err: "
+		return
+	}
+	br.Data = sources
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// FrequencyList
+// @Title 指标频度列表
+// @Description 指标频度列表
+// @Success 200 string "获取成功"
+// @router /edb_source/jiayue/frequency_list [get]
+func (this *JiaYueEdbSourceController) FrequencyList() {
+	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
+	}
+
+	frequencies, e := data.GetJiaYueFrequencyListFromBridge()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "桥接服务获取指标频度失败, Err: " + e.Error()
+		return
+	}
+	br.Data = frequencies
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// IndexPageList
+// @Title 指标列表-分页
+// @Description 指标列表-分页
+// @Param   PageSize  query  int  false  "每页数据条数"
+// @Param   CurrentIndex  query  int  false  "当前页码"
+// @Param   Source  query  int  true  "数据来源"
+// @Param   Frequency  query  string  false  "频度"
+// @Param   Keyword  query  string  false  "关键词: 指标ID/指标名称"
+// @Param   SortField  query  int  false  "排序字段: 1-指标开始时间; 2-指标最新时间; 3-更新时间"
+// @Param   SortRule  query  int  false  "排序方式: 1-正序; 2-倒序"
+// @Success 200 string "获取成功"
+// @router /edb_source/jiayue/index_page_list [get]
+func (this *JiaYueEdbSourceController) IndexPageList() {
+	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
+	}
+	source, _ := this.GetInt("Source")
+	frequency := this.GetString("Frequency")
+	keyword := this.GetString("Keyword")
+	sortField, _ := this.GetInt("SortField")
+	sortRule, _ := this.GetInt("SortRule")
+
+	if source <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, Source: %d", source)
+		return
+	}
+	cond := ` AND edb_source_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, source)
+	sourceItem, e := data_manage.GetEdbSourceItemByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "数据源有误"
+		br.ErrMsg = "获取数据源失败, Err: " + e.Error()
+		return
+	}
+
+	// 分页
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	var params data_manage.BridgeJiaYuePageIndexReq
+	params.PageIndex = currentIndex
+	params.PageSize = pageSize
+	params.Keyword = keyword
+	params.Frequency = frequency
+	params.SourceExtend = sourceItem.SourceExtend
+	params.SortField = sortField
+	params.SortRule = sortRule
+	total, list, e := data.GetJiaYueIndexPageListFromBridge(params)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标分页列表失败, Err: " + e.Error()
+		return
+	}
+	items := make([]*data_manage.DictIndexItem, 0)
+	for _, v := range list {
+		t := data_manage.FormatDictIndex2Item(v)
+		items = append(items, t)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(data_manage.JiaYueIndexPageListResp)
+	resp.Paging = page
+	resp.List = items
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 75 - 0
controllers/data_manage/my_chart.go

@@ -598,6 +598,20 @@ func (this *MyChartController) ClassifyDelete() {
 		return
 	}
 
+	nodeOb := new(data_manage.ChartFrameworkNode)
+	cond := ` AND my_chart_classify_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.MyChartClassifyId)
+	nodes, e := nodeOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取我的图表分类关联的框架节点失败, Err: " + e.Error()
+		return
+	}
+	if len(nodes) > 0 {
+		br.Msg = "该图分类已添加节点链接, 不允许删除"
+		return
+	}
 	//var condition string
 	//var pars []interface{}
 	//
@@ -1981,3 +1995,64 @@ func (this *MyChartController) CompanyPublicClassifyList() {
 //func init() {
 //	data.AddAllMyChartInfo()
 //}
+
+// ClassifyFrameworkNodeList
+// @Title 我的图表分类-关联的框架节点列表
+// @Description 我的图表分类-关联的框架节点列表
+// @Param   MyChartClassifyId	query	int	false	"图表分类ID"
+// @Success 200 Ret=200 获取成功
+// @router /classify/framework_node_list [get]
+func (this *MyChartController) ClassifyFrameworkNodeList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = true
+		}
+		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("MyChartClassifyId")
+	if classifyId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MyChartClassifyId: %d", classifyId)
+		return
+	}
+
+	_, e := data_manage.GetMyChartClassifyById(sysUser.AdminId, classifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取分类信息失败, Err:" + e.Error()
+		return
+	}
+
+	nodeOb := new(data_manage.ChartFrameworkNode)
+	cond := ` AND my_chart_classify_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, classifyId)
+	nodes, e := nodeOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取我的图表分类关联的框架节点失败, Err: " + e.Error()
+		return
+	}
+	resp := make([]*data_manage.ChartFrameworkNodeItem, 0)
+	for _, v := range nodes {
+		resp = append(resp, data_manage.FormatChartFrameworkNode2Item(v))
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Msg = "获取成功"
+	br.Success = true
+}

+ 1 - 0
controllers/data_manage/predict_edb_classify.go

@@ -251,6 +251,7 @@ func (this *PredictEdbClassifyController) Edit() {
 	br.Ret = 200
 	br.Msg = "保存成功"
 	br.Success = true
+	br.IsAddLog = true
 }
 
 // DeleteCheck

+ 63 - 3
controllers/report.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_api/models"
+	"eta/eta_api/models/company"
 	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
@@ -263,7 +264,6 @@ func (this *ReportController) PublishReport() {
 		br.ErrMsg = "参数错误,报告id不可为空"
 		return
 	}
-
 	reportArr := strings.Split(reportIds, ",")
 	tips := ""
 	for _, v := range reportArr {
@@ -289,7 +289,6 @@ func (this *ReportController) PublishReport() {
 		} else {
 			publishTime = time.Now()
 		}
-
 		var tmpErr error
 		if report.HasChapter == 1 && (report.ChapterType == utils.REPORT_TYPE_DAY || report.ChapterType == utils.REPORT_TYPE_WEEK) {
 			// 发布晨周报
@@ -309,6 +308,7 @@ func (this *ReportController) PublishReport() {
 				br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
 				return
 			}
+
 			go func() {
 				// 生成音频
 				if report.VideoUrl == "" {
@@ -321,6 +321,17 @@ func (this *ReportController) PublishReport() {
 				// 更新报告Es
 				_ = services.UpdateReportEs(report.Id, 2)
 			}()
+			recordItem := &models.ReportStateRecord{
+				ReportId:   vint,
+				ReportType: 1,
+				State:      2,
+				AdminId:    this.SysUser.AdminId,
+				AdminName:  this.SysUser.AdminName,
+				CreateTime: time.Now(),
+			}
+			go func() {
+				_, _ = models.AddReportStateRecord(recordItem)
+			}()
 		}
 	}
 	// 发布晨周报部分章节未发布的提示
@@ -378,9 +389,33 @@ func (this *ReportController) PublishCancleReport() {
 		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:      1,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
 	br.Ret = 200
 	br.Success = true
-	br.Msg = "取消发布成功"
 }
 
 // Delete
@@ -517,6 +552,18 @@ func (this *ReportController) Add() {
 		}()
 	}
 
+	recordItem := &models.ReportStateRecord{
+		ReportId:   int(newReportId),
+		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 = newReportId
 	resp.ReportCode = reportCode
@@ -602,6 +649,19 @@ func (this *ReportController) Edit() {
 		br.ErrMsg = "该报告已发布,不允许编辑"
 		return
 	}
+	if req.State != report.State {
+		recordItem := &models.ReportStateRecord{
+			ReportId:   int(req.ReportId),
+			ReportType: 1,
+			State:      req.State,
+			AdminId:    this.SysUser.AdminId,
+			AdminName:  this.SysUser.AdminName,
+			CreateTime: time.Now(),
+		}
+		go func() {
+			_, _ = models.AddReportStateRecord(recordItem)
+		}()
+	}
 
 	item := new(models.Report)
 	item.ClassifyIdFirst = req.ClassifyIdFirst

+ 1851 - 293
controllers/sandbox/sandbox.go

@@ -4,14 +4,15 @@ import (
 	"encoding/json"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/sandbox/request"
 	"eta/eta_api/models/sandbox/response"
-	"eta/eta_api/models/system"
 	sandboxService "eta/eta_api/services/sandbox"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
 )
 
 // versionSize 版本列表第一页数据约定是:3条
@@ -31,115 +32,115 @@ type SandboxController struct {
 // @Param   Keyword   query   string  false       "搜索关键词:沙盘名称/编辑人名称"
 // @Success 200 {object} response.SandboxListResp
 // @router /list [get]
-func (this *SandboxController) List() {
-	br := new(models.BaseResponse).Init()
-	defer func() {
-		this.Data["json"] = br
-		this.ServeJSON()
-	}()
-
-	chartPermissionId, _ := this.GetInt("ChartPermissionId")
-	keyword := this.GetString("Keyword")
-
-	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)
-
-	var condition string
-	var pars []interface{}
-
-	if chartPermissionId > 0 {
-		condition += " AND a.chart_permission_id=? "
-		pars = append(pars, chartPermissionId)
-	}
-
-	if keyword != "" {
-		condition += ` AND  ( a.name LIKE '%` + keyword + `%'  OR  b.name LIKE '%` + keyword + `%' )`
-	}
-
-	//获取指标信息
-	total, list, err := sandbox.GetList(condition, pars, startSize, pageSize)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Success = true
-		br.Msg = "获取沙盘列表失败"
-		br.ErrMsg = "获取沙盘列表失败,Err:" + err.Error()
-		return
-	}
-
-	if list == nil || (err != nil && err.Error() == utils.ErrNoRow()) {
-		list = make([]*sandbox.SandboxListItem, 0)
-	}
-
-	if len(list) > 0 {
-		sandboxIdList := make([]int, 0)
-		for _, v := range list {
-			sandboxIdList = append(sandboxIdList, v.SandboxId)
-		}
-
-		sandboxVersionTotalList, err := sandbox.GetTotalSandboxVersionBySandboxIdList(sandboxIdList)
-		if err != nil {
-			br.Success = true
-			br.Msg = "获取沙盘版本数量失败"
-			br.ErrMsg = "获取沙盘版本数量失败,Err:" + err.Error()
-			return
-		}
-		sandboxVersionTotalMap := make(map[int]int)
-
-		for _, v := range sandboxVersionTotalList {
-			sandboxVersionTotalMap[v.SandboxId] = v.Total
-		}
-
-		for _, item := range list {
-			/*key := fmt.Sprint(`crm:sandbox:edit:`, item.SandboxId)
-			opUserId, _ := utils.Rc.RedisInt(key)
-			//如果当前没有人操作,获取当前操作人是本人,那么编辑按钮可用
-			if opUserId <= 0 || (opUserId == this.SysUser.AdminId) {
-				item.CanEdit = true
-			} else {
-				adminInfo, errAdmin := system.GetSysUserById(opUserId)
-				if errAdmin != nil {
-					br.Msg = "获取失败"
-					br.ErrMsg = "获取失败,Err:" + errAdmin.Error()
-					return
-				}
-				item.Editor = adminInfo.RealName
-			}*/
-			markStatus, err := sandboxService.UpdateSandboxEditMark(item.SandboxId, this.SysUser.AdminId, 2, this.SysUser.RealName)
-			if err != nil {
-				br.Msg = "查询标记状态失败"
-				br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
-				return
-			}
-			if markStatus.Status == 0 {
-				item.CanEdit = true
-			} else {
-				item.Editor = markStatus.Editor
-			}
-
-			// 沙盘版本数量
-			versionTotal := sandboxVersionTotalMap[item.SandboxId]
-			item.VersionTotal = versionTotal
-		}
-	}
-
-	page := paging.GetPaging(currentIndex, pageSize, total)
-	resp := response.SandboxListResp{
-		Paging: page,
-		List:   list,
-	}
-	br.Ret = 200
-	br.Success = true
-	br.Msg = "获取成功"
-	br.Data = resp
-}
+//func (this *SandboxController) List() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	chartPermissionId, _ := this.GetInt("ChartPermissionId")
+//	keyword := this.GetString("Keyword")
+//
+//	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)
+//
+//	var condition string
+//	var pars []interface{}
+//
+//	if chartPermissionId > 0 {
+//		condition += " AND a.chart_permission_id=? "
+//		pars = append(pars, chartPermissionId)
+//	}
+//
+//	if keyword != "" {
+//		condition += ` AND  ( a.name LIKE '%` + keyword + `%'  OR  b.name LIKE '%` + keyword + `%' )`
+//	}
+//
+//	//获取指标信息
+//	total, list, err := sandbox.GetList(condition, pars, startSize, pageSize)
+//	if err != nil && err.Error() != utils.ErrNoRow() {
+//		br.Success = true
+//		br.Msg = "获取沙盘列表失败"
+//		br.ErrMsg = "获取沙盘列表失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	if list == nil || (err != nil && err.Error() == utils.ErrNoRow()) {
+//		list = make([]*sandbox.SandboxListItem, 0)
+//	}
+//
+//	if len(list) > 0 {
+//		sandboxIdList := make([]int, 0)
+//		for _, v := range list {
+//			sandboxIdList = append(sandboxIdList, v.SandboxId)
+//		}
+//
+//		sandboxVersionTotalList, err := sandbox.GetTotalSandboxVersionBySandboxIdList(sandboxIdList)
+//		if err != nil {
+//			br.Success = true
+//			br.Msg = "获取沙盘版本数量失败"
+//			br.ErrMsg = "获取沙盘版本数量失败,Err:" + err.Error()
+//			return
+//		}
+//		sandboxVersionTotalMap := make(map[int]int)
+//
+//		for _, v := range sandboxVersionTotalList {
+//			sandboxVersionTotalMap[v.SandboxId] = v.Total
+//		}
+//
+//		for _, item := range list {
+//			/*key := fmt.Sprint(`crm:sandbox:edit:`, item.SandboxId)
+//			opUserId, _ := utils.Rc.RedisInt(key)
+//			//如果当前没有人操作,获取当前操作人是本人,那么编辑按钮可用
+//			if opUserId <= 0 || (opUserId == this.SysUser.AdminId) {
+//				item.CanEdit = true
+//			} else {
+//				adminInfo, errAdmin := system.GetSysUserById(opUserId)
+//				if errAdmin != nil {
+//					br.Msg = "获取失败"
+//					br.ErrMsg = "获取失败,Err:" + errAdmin.Error()
+//					return
+//				}
+//				item.Editor = adminInfo.RealName
+//			}*/
+//			markStatus, err := sandboxService.UpdateSandboxEditMark(item.SandboxId, this.SysUser.AdminId, 2, this.SysUser.RealName)
+//			if err != nil {
+//				br.Msg = "查询标记状态失败"
+//				br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+//				return
+//			}
+//			if markStatus.Status == 0 {
+//				item.CanEdit = true
+//			} else {
+//				item.Editor = markStatus.Editor
+//			}
+//
+//			// 沙盘版本数量
+//			versionTotal := sandboxVersionTotalMap[item.SandboxId]
+//			item.VersionTotal = versionTotal
+//		}
+//	}
+//
+//	page := paging.GetPaging(currentIndex, pageSize, total)
+//	resp := response.SandboxListResp{
+//		Paging: page,
+//		List:   list,
+//	}
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = resp
+//}
 
 // FirstVersionList
 // @Title 逻辑导图版本列表(列表页第一页)
@@ -299,82 +300,82 @@ func (this *SandboxController) VersionList() {
 	br.Data = resp
 }
 
-// Save
-// @Title 新增/编辑保存沙盘
-// @Description 新增/编辑保存沙盘接口
-// @Param	request	body request.AddAndEditSandbox true "type json string"
-// @Success 200 {object} sandbox.Sandbox
-// @router /save [post]
-func (this *SandboxController) Save() {
-	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 request.AddAndEditSandbox
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
-	}
-
-	var sandboxResp *sandbox.SandboxSaveResp
-	// 获取系统菜单, 如果没有对应的字段的特殊处理项, 则忽略必填
-	menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
-	if e != nil {
-		br.Msg = "保存失败"
-		br.ErrMsg = "获取菜单列表失败, Err: " + e.Error()
-		return
-	}
-	menuMap := make(map[string]bool)
-	for _, m := range menus {
-		if m.ButtonCode != "" {
-			menuMap[m.ButtonCode] = true
-		}
-	}
-	ignoreVariety := false
-	if !menuMap[system.MenuSpecialHandleSandboxVariety] {
-		ignoreVariety = true
-	}
-
-	var errMsg string
-	if req.SandboxVersionCode == `` {
-		//新增沙盘
-		sandboxResp, err = sandboxService.AddSandbox(req, sysUser.AdminId, sysUser.RealName, ignoreVariety)
-	} else {
-		////更新当前编辑中的状态缓存
-		//err = sandboxService.UpdateSandboxEditMark(req.SandboxId, sysUser.AdminId, 1)
-		//if err != nil {
-		//	br.Msg = err.Error()
-		//	return
-		//}
-
-		//编辑沙盘
-		sandboxResp, err, errMsg = sandboxService.UpdateSandbox(req, sysUser.AdminId, sysUser.RealName, ignoreVariety)
-	}
-	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
-	br.Data = sandboxResp
-}
+//// Save
+//// @Title 新增/编辑保存沙盘
+//// @Description 新增/编辑保存沙盘接口
+//// @Param	request	body request.AddAndEditSandbox true "type json string"
+//// @Success 200 {object} sandbox.Sandbox
+//// @router /save [post]
+//func (this *SandboxController) Save() {
+//	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 request.AddAndEditSandbox
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	var sandboxResp *sandbox.SandboxSaveResp
+//	// 获取系统菜单, 如果没有对应的字段的特殊处理项, 则忽略必填
+//	menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
+//	if e != nil {
+//		br.Msg = "保存失败"
+//		br.ErrMsg = "获取菜单列表失败, Err: " + e.Error()
+//		return
+//	}
+//	menuMap := make(map[string]bool)
+//	for _, m := range menus {
+//		if m.ButtonCode != "" {
+//			menuMap[m.ButtonCode] = true
+//		}
+//	}
+//	ignoreVariety := false
+//	if !menuMap[system.MenuSpecialHandleSandboxVariety] {
+//		ignoreVariety = true
+//	}
+//
+//	var errMsg string
+//	if req.SandboxVersionCode == `` {
+//		//新增沙盘
+//		sandboxResp, err = sandboxService.AddSandbox(req, sysUser.AdminId, sysUser.RealName, ignoreVariety)
+//	} else {
+//		////更新当前编辑中的状态缓存
+//		//err = sandboxService.UpdateSandboxEditMark(req.SandboxId, sysUser.AdminId, 1)
+//		//if err != nil {
+//		//	br.Msg = err.Error()
+//		//	return
+//		//}
+//
+//		//编辑沙盘
+//		sandboxResp, err, errMsg = sandboxService.UpdateSandbox(req, sysUser.AdminId, sysUser.RealName, ignoreVariety)
+//	}
+//	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
+//	br.Data = sandboxResp
+//}
 
 // AddSandboxDraft
 // @Title 添加沙盘草稿
@@ -640,111 +641,111 @@ func (this *SandboxController) Delete() {
 	br.Msg = msg
 }
 
-// DeleteVersion
-// @Title 删除沙盘版本
-// @Description 删除沙盘版本接口
-// @Param	request	body request.DeleteSandbox true "type json string"
-// @Success 200 标记成功
-// @router /version/delete [post]
-func (this *SandboxController) DeleteVersion() {
-	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 request.DeleteSandboxVersion
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
-	}
-
-	if req.SandboxVersionCode == `` {
-		br.Msg = "缺少沙盘版本号"
-		return
-	}
-	//删除沙盘
-	err, errMsg := sandboxService.DeleteSandboxVersion(req.SandboxVersionCode, this.SysUser.AdminId)
-	if err != nil {
-		br.Msg = "删除版本失败"
-		if errMsg != `` {
-			br.Msg = errMsg
-		}
-		br.ErrMsg = err.Error()
-		return
-	}
-
-	msg := "删除成功"
-	br.Ret = 200
-	br.Success = true
-	br.Msg = msg
-}
-
-// ResetDraftToLastVersion
-// @Title 重置沙盘草稿至最新版本
-// @Description 重置沙盘草稿至最新版本接口
-// @Param	request	body request.DeleteSandbox true "type json string"
-// @Success 200 {object} sandbox.SandboxDraft
-// @Fail 202 另外的人在操作,不要重复添加草稿;204 错误了,当时不是必要性的错误,不用将错误信息暴露给用户
-// @router /draft/reset [post]
-func (this *SandboxController) ResetDraftToLastVersion() {
-	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 request.DeleteSandbox
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
-	}
-
-	if req.SandboxId <= 0 {
-		br.Msg = "缺少沙盘编号"
-		return
-	}
-
-	//更新标记key
-	markStatus, err := sandboxService.UpdateSandboxEditMark(req.SandboxId, sysUser.AdminId, 0, sysUser.RealName)
-	if err != nil {
-		br.Msg = err.Error()
-		return
-	}
-	if markStatus.Status == 1 {
-		br.Msg = markStatus.Msg
-		return
-	}
-
-	//重置沙盘草稿至最新版本
-	sandboxDraftInfo, err := sandboxService.ResetDraftToLastVersion(req.SandboxId, sysUser.AdminId, sysUser.RealName)
-	if err != nil {
-		br.Msg = "保存失败!"
-		br.ErrMsg = "保存失败,Err:" + err.Error()
-		return
-	}
-	msg := "保存成功"
-	br.Ret = 200
-	br.Success = true
-	br.Msg = msg
-	br.Data = sandboxDraftInfo
-}
+//// DeleteVersion
+//// @Title 删除沙盘版本
+//// @Description 删除沙盘版本接口
+//// @Param	request	body request.DeleteSandbox true "type json string"
+//// @Success 200 标记成功
+//// @router /version/delete [post]
+//func (this *SandboxController) DeleteVersion() {
+//	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 request.DeleteSandboxVersion
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	if req.SandboxVersionCode == `` {
+//		br.Msg = "缺少沙盘版本号"
+//		return
+//	}
+//	//删除沙盘
+//	err, errMsg := sandboxService.DeleteSandboxVersion(req.SandboxVersionCode, this.SysUser.AdminId)
+//	if err != nil {
+//		br.Msg = "删除版本失败"
+//		if errMsg != `` {
+//			br.Msg = errMsg
+//		}
+//		br.ErrMsg = err.Error()
+//		return
+//	}
+//
+//	msg := "删除成功"
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = msg
+//}
+
+//// ResetDraftToLastVersion
+//// @Title 重置沙盘草稿至最新版本
+//// @Description 重置沙盘草稿至最新版本接口
+//// @Param	request	body request.DeleteSandbox true "type json string"
+//// @Success 200 {object} sandbox.SandboxDraft
+//// @Fail 202 另外的人在操作,不要重复添加草稿;204 错误了,当时不是必要性的错误,不用将错误信息暴露给用户
+//// @router /draft/reset [post]
+//func (this *SandboxController) ResetDraftToLastVersion() {
+//	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 request.DeleteSandbox
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	if req.SandboxId <= 0 {
+//		br.Msg = "缺少沙盘编号"
+//		return
+//	}
+//
+//	//更新标记key
+//	markStatus, err := sandboxService.UpdateSandboxEditMark(req.SandboxId, sysUser.AdminId, 0, sysUser.RealName)
+//	if err != nil {
+//		br.Msg = err.Error()
+//		return
+//	}
+//	if markStatus.Status == 1 {
+//		br.Msg = markStatus.Msg
+//		return
+//	}
+//
+//	//重置沙盘草稿至最新版本
+//	sandboxDraftInfo, err := sandboxService.ResetDraftToLastVersion(req.SandboxId, sysUser.AdminId, sysUser.RealName)
+//	if err != nil {
+//		br.Msg = "保存失败!"
+//		br.ErrMsg = "保存失败,Err:" + err.Error()
+//		return
+//	}
+//	msg := "保存成功"
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = msg
+//	br.Data = sandboxDraftInfo
+//}
 
 // ListByQuote
 // @Title 逻辑导图列表(其他地方引用到的,莫名其妙要根据输入的关键字匹配品种)
@@ -786,7 +787,7 @@ func (this *SandboxController) ListByQuote() {
 	}
 
 	if keyword != "" {
-		condition += ` AND  ( a.name LIKE '%` + keyword + `%'  OR  b.name LIKE '%` + keyword + `%' OR  a.chart_permission_name LIKE '%` + keyword + `%' )`
+		condition += ` AND  ( a.name LIKE '%` + keyword + `%' OR  a.chart_permission_name LIKE '%` + keyword + `%' )`
 	}
 
 	//获取指标信息
@@ -799,7 +800,7 @@ func (this *SandboxController) ListByQuote() {
 	}
 
 	if list == nil || (err != nil && err.Error() == utils.ErrNoRow()) {
-		list = make([]*sandbox.SandboxListItem, 0)
+		list = make([]*sandbox.Sandbox, 0)
 	}
 
 	page := paging.GetPaging(currentIndex, pageSize, total)
@@ -812,3 +813,1560 @@ func (this *SandboxController) ListByQuote() {
 	br.Msg = "获取成功"
 	br.Data = resp
 }
+
+// SandboxClassifyItems
+// @Title 获取所有沙盘分类接口-包含沙盘
+// @Description 获取所有沙盘分类接口-包含沙盘
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /classify/list [get]
+func (this *SandboxController) SandboxClassifyItems() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := new(sandbox.SandboxClassifyListResp)
+	sandboxClassifyId, _ := this.GetInt("SandboxClassifyId")
+
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		errMsg, err := sandboxService.GetSandboxClassifyListForMe(*this.SysUser, resp, sandboxClassifyId)
+		if err != nil {
+			br.Msg = errMsg
+			br.ErrMsg = err.Error()
+			return
+		}
+		// 移除没有权限的图表
+		//allNodes := sandboxService.HandleNoPermissionSandbox(resp.AllNodes, nil)
+		//resp.AllNodes = allNodes
+
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		fmt.Println("source my classify")
+		return
+	}
+
+	rootList, err := sandbox.GetSandboxClassifyAndInfoByParentId(sandboxClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	classifyAll, err := sandbox.GetSandboxClassifyAndInfoByParentId(sandboxClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	//sandboxAll, err := sandbox.GetSandboxItemsByClassifyId(sandboxClassifyId)
+	//if err != nil && err.Error() != utils.ErrNoRow() {
+	//	br.Msg = "获取失败"
+	//	br.ErrMsg = "获取数据失败,Err:" + err.Error()
+	//	return
+	//}
+
+	//sandListMap := make(map[int][]*sandbox.SandboxClassifyItems)
+	//for _, v := range sandboxAll {
+	//	if _, ok := sandListMap[v.SandboxClassifyId]; !ok {
+	//		list := make([]*sandbox.SandboxClassifyItems, 0)
+	//		list = append(list, v)
+	//		sandListMap[v.SandboxClassifyId] = list
+	//	} else {
+	//		sandListMap[v.SandboxClassifyId] = append(sandListMap[v.SandboxClassifyId], v)
+	//	}
+	//}
+
+	nodeAll := make([]*sandbox.SandboxClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		sandboxService.SandboxClassifyItemsMakeTreeV2(this.SysUser, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+
+	//newAll := sandboxService.SandboxItemsMakeTree(nodeAll, sandListMap, sandboxClassifyId)
+
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 新增沙盘分类
+// @Description 新增沙盘分类接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /classify/add [post]
+func (this *SandboxController) AddSandboxClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req sandbox.AddSandboxClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SandboxClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	count, err := sandbox.GetSandboxClassifyCount(req.SandboxClassifyName, req.ParentId)
+	if err != nil {
+		br.Msg = "判断名称是否已存在失败"
+		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "分类名称已存在,请重新输入"
+		br.IsSendEmail = false
+		return
+	}
+	//获取该层级下最大的排序数
+	maxSort, err := sandbox.GetSandboxClassifyMaxSort(req.ParentId)
+
+	classify := new(sandbox.SandboxClassify)
+	classify.ParentId = req.ParentId
+	classify.SandboxClassifyName = req.SandboxClassifyName
+	classify.HasData = 0
+	classify.CreateTime = time.Now()
+	classify.ModifyTime = time.Now()
+	classify.SysUserId = this.SysUser.AdminId
+	classify.SysUserRealName = this.SysUser.RealName
+	classify.ChartPermissionId = req.ChartPermissionId
+	classify.ChartPermissionName = req.ChartPermissionName
+	classify.Level = req.Level + 1
+	classify.Sort = maxSort + 1
+
+	_, err = sandbox.AddSandboxClassify(classify)
+	if err != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+}
+
+// @Title 修改沙盘分类
+// @Description 修改沙盘分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /classify/edit [post]
+func (this *SandboxController) EditSandboxClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req sandbox.EditSandboxClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SandboxClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.SandboxClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	item, err := sandbox.GetSandboxClassifyById(req.SandboxClassifyId)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.Msg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	if item.SandboxClassifyName != req.SandboxClassifyName {
+		count, err := sandbox.GetSandboxClassifyCount(req.SandboxClassifyName, item.ParentId)
+		if err != nil {
+			br.Msg = "判断名称是否已存在失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在,请重新输入"
+			br.IsSendEmail = false
+			return
+		}
+
+		err = sandbox.EditSandboxClassify(req.SandboxClassifyId, req.SandboxClassifyName)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+	}
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// @Title 删除沙盘检测接口
+// @Description 删除沙盘检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /classify/delete/check [post]
+func (this *SandboxController) DeleteSandboxClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req sandbox.SandboxClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.SandboxClassifyId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+	//删除分类
+	if req.SandboxClassifyId > 0 {
+		//判断沙盘分类下,是否含有沙盘
+		count, err := sandbox.GetSandboxInfoCountByClassifyId(req.SandboxClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有指标失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联沙盘不可删除"
+		}
+	}
+
+	if deleteStatus != 1 {
+		classifyCount, err := sandbox.GetSandboxInfoCountByClassifyId(req.SandboxClassifyId)
+		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(sandbox.SandboxClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// @Title 删除沙盘分类/沙盘
+// @Description 删除沙盘分类/沙盘接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /classify/delete [post]
+func (this *SandboxController) DeleteSandboxClassify() {
+	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 sandbox.DeleteSandboxClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.SandboxClassifyId < 0 && req.SandboxId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	//删除分类
+	if req.SandboxClassifyId > 0 && req.SandboxId == 0 {
+		//判断是否含有指标
+		count, err := sandbox.GetSandboxInfoCountByClassifyId(req.SandboxId)
+		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 = sandbox.DeleteSandboxClassify(req.SandboxClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	//删除沙盘
+	if req.SandboxId > 0 {
+		sandboxInfo, err := sandbox.GetSandboxById(req.SandboxId)
+		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 sandboxInfo == nil {
+			br.Msg = "沙盘已删除,请刷新页面"
+			return
+		}
+		err = sandboxService.DeleteSandbox(req.SandboxId)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+	}
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// ChartClassifyMove
+// @Title 沙盘分类移动接口
+// @Description 沙盘分类移动接口
+// @Success 200 {object} data_manage.MoveChartClassifyReq
+// @router /classify/move [post]
+func (this *SandboxController) ChartClassifyMove() {
+	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 sandbox.MoveSandboxClassifyReq
+	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
+	}
+	//判断分类是否存在
+	sandboxClassifyInfo, err := sandbox.GetSandboxClassifyById(req.ClassifyId)
+	if err != nil {
+		br.Msg = "移动失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	updateCol := make([]string, 0)
+
+	// 判断移动的是分类还是沙盘
+	if req.SandboxId > 0 {
+		//判断分类是否存在
+		count, _ := sandbox.GetSandboxClassifyCountById(req.ClassifyId)
+		if count <= 0 {
+			br.Msg = "分类已被删除,不可移动,请刷新页面"
+			return
+		}
+
+		sandboxInfo, err := sandbox.GetSandboxById(req.SandboxId)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取沙盘信息失败,Err:" + err.Error()
+			return
+		}
+
+		//如果改变了分类,那么移动该图表数据
+		if sandboxInfo.SandboxClassifyId != req.ParentClassifyId {
+			//查询需要修改的分类下是否存在同一个图表名称
+			tmpSandboxInfo, tmpErr := sandbox.GetSandboxByClassifyIdAndName(req.ParentClassifyId, sandboxInfo.Name)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				br.Msg = "移动失败"
+				br.ErrMsg = "移动失败,Err:" + tmpErr.Error()
+				return
+			}
+			if tmpSandboxInfo != nil {
+				br.Msg = "移动失败,同一个分类下沙盘名称不允许重复"
+				br.ErrMsg = "移动失败,同一个分类下沙盘名称不允许重复"
+				return
+			}
+			err = sandbox.MoveSandbox(req.SandboxId, req.ParentClassifyId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "移动失败,Err:" + err.Error()
+				return
+			}
+		}
+
+		//移动排序
+		updateCol := make([]string, 0)
+		//如果有传入 上一个兄弟节点分类id
+		if req.PrevId > 0 {
+			if req.PrevType == 1 {
+				//上一个兄弟节点
+				prevClassify, err := sandbox.GetSandboxClassifyById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是分类 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := sandbox.GetSandboxClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+						if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == sandboxClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, prevClassify.SandboxClassifyId, prevClassify.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是分类 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := sandbox.GetSandboxById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(分类)与下一个兄弟(沙盘)的排序权重是一致的,那么需要将下一个兄弟(沙盘)(以及下个兄弟(沙盘)的同样排序权重)的排序权重+2,自己变成上一个兄弟(分类)的排序权重+1
+						if prevClassify.Sort == nextChartInfo.Sort || prevClassify.Sort == sandboxInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, prevClassify.SandboxClassifyId, prevClassify.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟(沙盘)的排序权重正好是上个兄弟节点(分类)的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.SandboxClassifyId, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					}
+
+				}
+
+				sandboxInfo.Sort = prevClassify.Sort + 1
+				sandboxInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+
+			} else {
+				prevSandbox, err := sandbox.GetSandboxById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是沙盘 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := sandbox.GetSandboxClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevSandbox.Sort == nextClassify.Sort || prevSandbox.Sort == sandboxClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevSandbox.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是沙盘 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := sandbox.GetSandboxById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevSandbox.Sort == nextChartInfo.Sort || prevSandbox.Sort == sandboxInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevSandbox.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+							}
+						}
+					}
+
+				}
+
+				sandboxInfo.Sort = prevSandbox.Sort + 1
+				sandboxInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+			}
+
+		} else {
+			// prevId为0,也就是沙盘移到最前端
+			firstClassify, err := sandbox.GetFirstSandboxByClassifyId(req.ClassifyId)
+			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 `
+				_ = sandbox.UpdateSandboxSortByClassifyId(firstClassify.SandboxClassifyId, 0, firstClassify.SandboxId-1, updateSortStr)
+			}
+
+			sandboxInfo.Sort = 0 //那就是排在第一位
+			sandboxInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = sandboxInfo.Update(updateCol)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "修改失败,Err:" + err.Error()
+				return
+			}
+		}
+	} else {
+		//移动的是分类
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if sandboxClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+			parentChartClassifyInfo, err := sandbox.GetSandboxClassifyById(req.ParentClassifyId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+				return
+			}
+			sandboxClassifyInfo.ParentId = parentChartClassifyInfo.SandboxClassifyId
+			sandboxClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+			sandboxClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+		} else if sandboxClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId == 0 {
+			//改为一级分类
+			sandboxClassifyInfo.ParentId = req.ParentClassifyId
+			sandboxClassifyInfo.Level = 1
+			sandboxClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+		}
+
+		//如果有传入 上一个兄弟节点分类id
+		if req.PrevId > 0 {
+			if req.PrevType == 1 {
+				//上一个节点是分类
+				//上一个兄弟节点
+				prevClassify, err := sandbox.GetSandboxClassifyById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是分类 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := sandbox.GetSandboxClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+						if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == sandboxClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, prevClassify.SandboxClassifyId, prevClassify.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是分类 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := sandbox.GetSandboxById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(分类)与下一个兄弟(沙盘)的排序权重是一致的,那么需要将下一个兄弟(沙盘)(以及下个兄弟(沙盘)的同样排序权重)的排序权重+2,自己变成上一个兄弟(分类)的排序权重+1
+						if prevClassify.Sort == nextChartInfo.Sort || prevClassify.Sort == sandboxClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, prevClassify.SandboxClassifyId, prevClassify.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟(沙盘)的排序权重正好是上个兄弟节点(分类)的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.SandboxClassifyId, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevClassify.SandboxClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					}
+
+				}
+
+				sandboxClassifyInfo.Sort = prevClassify.Sort + 1
+				sandboxClassifyInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+
+			} else {
+				//上一个节点是沙盘
+				prevSandbox, err := sandbox.GetSandboxById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是沙盘 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := sandbox.GetSandboxClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevSandbox.Sort == nextClassify.Sort || prevSandbox.Sort == sandboxClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevSandbox.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是沙盘 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := sandbox.GetSandboxById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevSandbox.Sort == nextChartInfo.Sort || prevSandbox.Sort == sandboxClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+							_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevSandbox.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = sandbox.UpdateSandboxClassifySortByParentId(prevSandbox.SandboxClassifyId, 0, prevSandbox.Sort, updateSortStr)
+								_ = sandbox.UpdateSandboxSortByClassifyId(prevSandbox.SandboxClassifyId, prevSandbox.Sort, prevSandbox.SandboxId, updateSortStr)
+							}
+						}
+					}
+
+				}
+				sandboxClassifyInfo.Sort = prevSandbox.Sort + 1
+				sandboxClassifyInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+
+			}
+
+		} else {
+			firstClassify, err := sandbox.GetFirstSandboxClassifyByParentId(sandboxClassifyInfo.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 `
+				_ = sandbox.UpdateSandboxClassifySortByParentId(firstClassify.ParentId, firstClassify.SandboxClassifyId-1, 0, updateSortStr)
+			}
+
+			sandboxClassifyInfo.Sort = 0 //那就是排在第一位
+			sandboxClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = sandboxClassifyInfo.Update(updateCol)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "修改失败,Err:" + err.Error()
+				return
+			}
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+// @Title ETA图表列表接口
+// @Description ETA图表列表接口
+// @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"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /listV2 [get]
+func (this *SandboxController) ListV2() {
+	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
+	}
+
+	sandboxClassifyId, _ := this.GetInt("SandboxClassifyId")
+
+	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{}
+
+	if sandboxClassifyId > 0 {
+		sandboxClassifyId, err := sandbox.GetSandboxClassify(sandboxClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取图表信息失败"
+			br.ErrMsg = "获取信息失败,GetChartClassify,Err:" + err.Error()
+			return
+		}
+		condition += " AND sandbox_classify_id IN(" + sandboxClassifyId + ") "
+		//pars = append(pars, chartClassifyId)
+	}
+	if keyWord != "" {
+		condition += ` AND  ( name LIKE '%` + keyWord + `%' )`
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		condition += ` AND sys_user_id = ? `
+		pars = append(pars, sysUser.AdminId)
+	}
+
+	//获取图表信息
+	condition += ` AND is_delete = 0 `
+	list, err := sandbox.GetSandboxListByCondition(condition, pars, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取沙盘信息失败"
+		br.ErrMsg = "获取沙盘信息失败,Err:" + err.Error()
+		return
+	}
+
+	for i, v := range list {
+		ids, err := sandbox.GetSandboxAllParentByClassifyId(v.SandboxClassifyId)
+		if err != nil {
+			br.Msg = "获取父级信息错误!"
+			br.ErrMsg = "获取父级信息错误,Err:" + err.Error()
+			return
+		}
+		list[i].ParentIds = ids
+	}
+	resp := new(sandbox.SandboxListResp)
+	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
+		items := make([]*sandbox.SandboxListItems, 0)
+		resp.Paging = page
+		resp.List = items
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	dataCount, err := sandbox.GetSandboxListCountByCondition(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
+}
+
+// Save
+// @Title 新增/编辑保存沙盘
+// @Description 新增/编辑保存沙盘接口
+// @Param	request	body request.AddAndEditSandbox true "type json string"
+// @Success 200 {object} sandbox.Sandbox
+// @router /saveV2 [post]
+func (this *SandboxController) SaveV2() {
+	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 request.AddAndEditSandboxV2
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	var sandboxResp *sandbox.SandboxSaveResp
+
+	var errMsg string
+
+	if req.SandboxId <= 0 {
+		//新增沙盘
+		sandboxResp, err = sandboxService.AddSandboxV2(req, sysUser.AdminId, sysUser.RealName)
+		if err != nil {
+			br.Msg = "保存失败!"
+			if errMsg != `` {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		//编辑沙盘
+		sandboxInfo := &sandbox.Sandbox{
+			SandboxId:         req.SandboxId,
+			Name:              utils.TrimStr(req.Name),
+			Content:           req.Content,
+			MindmapData:       req.MindmapData,
+			PicUrl:            utils.TrimStr(req.PicUrl),
+			ModifyTime:        time.Now(),
+			SandboxClassifyId: req.SandboxClassifyId,
+		}
+		//缩略图为空时不更新
+		var updateSandboxColumn = []string{}
+		if req.PicUrl == ""{
+			updateSandboxColumn = []string{"Content", "MindmapData", "ModifyTime", "SandboxClassifyId"}
+		} else {
+			updateSandboxColumn = []string{"Content", "MindmapData", "PicUrl", "ModifyTime", "SandboxClassifyId"}
+		}
+		err = sandboxInfo.Update(updateSandboxColumn)
+		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
+	br.Data = sandboxResp
+}
+
+// Delete
+// @Title 删除沙盘
+// @Description 删除沙盘接口
+// @Param	request	body request.DeleteSandbox true "type json string"
+// @Success 200 标记成功
+// @router /deleteV2 [post]
+func (this *SandboxController) DeleteV2() {
+	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 request.DeleteSandbox
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.SandboxId <= 0 {
+		br.Msg = "缺少沙盘编号"
+		return
+	}
+
+	//删除沙盘
+	err = sandboxService.DeleteSandbox(req.SandboxId)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	msg := "删除成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// GetSandboxVersionDetail
+// @Title 获取沙盘版本数据详情(已保存的)
+// @Description 获取沙盘版本数据详情接口(已保存的)
+// @Param   SandboxVersionCode   query   string  true       "沙盘版本code"
+// @Success 200 {object} sandbox.SandboxVersion
+// @router /detail [get]
+func (this *SandboxController) GetSandboxDetail() {
+	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
+	}
+
+	sandboxId, _ := this.GetInt("SandboxId")
+	if sandboxId == 0 {
+		br.Msg = "缺少沙盘Id"
+		return
+	}
+
+	//获取沙盘数据详情(已保存的)
+	sandboxVersionInfo, err := sandbox.GetSandboxById(sandboxId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	msg := "获取成功"
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+	br.Data = sandboxVersionInfo
+}
+
+//// SandboxClassifyItems
+//// @Title 获取所有沙盘分类接口-包含沙盘-先分类后沙盘区分,暂时弃用
+//// @Description 获取所有沙盘分类接口-包含沙盘
+//// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+//// @Success 200 {object} data_manage.ChartClassifyListResp
+//// @router /classify/list [get]
+//func (this *SandboxController) SandboxClassifyItems() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//
+//	resp := new(sandbox.SandboxClassifyListResp)
+//	sandboxClassifyId, _ := this.GetInt("SandboxClassifyId")
+//
+//	isShowMe, _ := this.GetBool("IsShowMe")
+//	if isShowMe {
+//		errMsg, err := sandboxService.GetSandboxClassifyListForMe(*this.SysUser, resp, sandboxClassifyId)
+//		if err != nil {
+//			br.Msg = errMsg
+//			br.ErrMsg = err.Error()
+//			return
+//		}
+//		// 移除没有权限的图表
+//		//allNodes := sandboxService.HandleNoPermissionSandbox(resp.AllNodes, nil)
+//		//resp.AllNodes = allNodes
+//
+//		br.Ret = 200
+//		br.Success = true
+//		br.Msg = "获取成功"
+//		br.Data = resp
+//		fmt.Println("source my classify")
+//		return
+//	}
+//
+//	rootList, err := sandbox.GetSandboxClassifyByParentId(sandboxClassifyId)
+//	if err != nil && err.Error() != utils.ErrNoRow() {
+//		br.Msg = "获取失败"
+//		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	classifyAll, err := sandbox.GetSandboxClassifyByParentId(sandboxClassifyId)
+//	if err != nil && err.Error() != utils.ErrNoRow() {
+//		br.Msg = "获取失败"
+//		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	sandboxAll, err := sandbox.GetSandboxItemsByClassifyId(sandboxClassifyId)
+//	if err != nil && err.Error() != utils.ErrNoRow() {
+//		br.Msg = "获取失败"
+//		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	sandListMap := make(map[int][]*sandbox.SandboxClassifyItems)
+//	for _, v := range sandboxAll {
+//		if _, ok := sandListMap[v.SandboxClassifyId]; !ok {
+//			list := make([]*sandbox.SandboxClassifyItems, 0)
+//			list = append(list, v)
+//			sandListMap[v.SandboxClassifyId] = list
+//		} else {
+//			sandListMap[v.SandboxClassifyId] = append(sandListMap[v.SandboxClassifyId], v)
+//		}
+//	}
+//
+//	nodeAll := make([]*sandbox.SandboxClassifyItems, 0)
+//	for k := range rootList {
+//		rootNode := rootList[k]
+//		sandboxService.SandboxClassifyItemsMakeTree(this.SysUser, classifyAll, rootNode)
+//		nodeAll = append(nodeAll, rootNode)
+//	}
+//	//for k := range nodeAll {
+//	//
+//	//}
+//	newAll := sandboxService.SandboxItemsMakeTree(nodeAll, sandListMap, sandboxClassifyId)
+//
+//	resp.AllNodes = newAll
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "获取成功"
+//	br.Data = resp
+//}
+
+//// SandboxMove
+//// @Title 移动沙盘接口
+//// @Description 移动图表接口
+//// @Success 200 {object} data_manage.MoveChartInfoReq
+//// @router /move [post]
+//func (this *SandboxController) SandboxMove() {
+//	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 sandbox.MoveSandboxReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	if req.SandboxId <= 0 {
+//		br.Msg = "参数错误"
+//		br.ErrMsg = "沙盘id小于等于0"
+//		return
+//	}
+//
+//	if req.SandboxClassifyId <= 0 {
+//		br.Msg = "请选择分类"
+//		return
+//	}
+//	//判断分类是否存在
+//	count, _ := sandbox.GetSandboxClassifyCountById(req.SandboxClassifyId)
+//	if count <= 0 {
+//		br.Msg = "分类已被删除,不可移动,请刷新页面"
+//		return
+//	}
+//
+//	sandboxInfo, err := sandbox.GetSandboxById(req.SandboxId)
+//	if err != nil {
+//		br.Msg = "移动失败"
+//		br.ErrMsg = "获取沙盘信息失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	//如果改变了分类,那么移动该图表数据
+//	if sandboxInfo.SandboxClassifyId != req.SandboxClassifyId {
+//		//查询需要修改的分类下是否存在同一个图表名称
+//		tmpSandboxInfo, tmpErr := sandbox.GetSandboxByClassifyIdAndName(req.SandboxClassifyId, sandboxInfo.Name)
+//		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "移动失败,Err:" + tmpErr.Error()
+//			return
+//		}
+//		if tmpSandboxInfo != nil {
+//			br.Msg = "移动失败,同一个分类下沙盘名称不允许重复"
+//			br.ErrMsg = "移动失败,同一个分类下沙盘名称不允许重复"
+//			return
+//		}
+//		err = sandbox.MoveSandbox(req.SandboxId, req.SandboxClassifyId)
+//		if err != nil {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "移动失败,Err:" + err.Error()
+//			return
+//		}
+//	}
+//
+//	//移动排序
+//	updateCol := make([]string, 0)
+//	//如果有传入 上一个兄弟节点分类id
+//	if req.PrevSandboxId > 0 {
+//		prevChartInfo, err := sandbox.GetSandboxById(req.PrevSandboxId)
+//		if err != nil {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+//			return
+//		}
+//
+//		//如果是移动在两个兄弟节点之间
+//		if req.NextSandboxId > 0 {
+//			//下一个兄弟节点
+//			nextChartInfo, err := sandbox.GetSandboxById(req.NextSandboxId)
+//			if err != nil {
+//				br.Msg = "移动失败"
+//				br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+//				return
+//			}
+//			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+//			if prevChartInfo.Sort == nextChartInfo.Sort || prevChartInfo.Sort == sandboxInfo.Sort {
+//				//变更兄弟节点的排序
+//				updateSortStr := `sort + 2`
+//				_ = sandbox.UpdateSandboxSortByClassifyId(prevChartInfo.SandboxClassifyId, prevChartInfo.Sort, prevChartInfo.SandboxId, updateSortStr)
+//			} else {
+//				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+//				if nextChartInfo.Sort-prevChartInfo.Sort == 1 {
+//					//变更兄弟节点的排序
+//					updateSortStr := `sort + 1`
+//					_ = sandbox.UpdateSandboxSortByClassifyId(prevChartInfo.SandboxClassifyId, prevChartInfo.Sort, prevChartInfo.SandboxId, updateSortStr)
+//				}
+//			}
+//		}
+//
+//		sandboxInfo.Sort = prevChartInfo.Sort + 1
+//		sandboxInfo.ModifyTime = time.Now()
+//		updateCol = append(updateCol, "Sort", "ModifyTime")
+//
+//	} else {
+//		firstClassify, err := sandbox.GetFirstSandboxByClassifyId(req.SandboxClassifyId)
+//		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 `
+//			_ = sandbox.UpdateSandboxSortByClassifyId(firstClassify.SandboxClassifyId, 0, firstClassify.SandboxId-1, updateSortStr)
+//		}
+//
+//		sandboxInfo.Sort = 0 //那就是排在第一位
+//		sandboxInfo.ModifyTime = time.Now()
+//		updateCol = append(updateCol, "Sort", "ModifyTime")
+//	}
+//
+//	//更新
+//	if len(updateCol) > 0 {
+//		err = sandboxInfo.Update(updateCol)
+//		if err != nil {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "修改失败,Err:" + err.Error()
+//			return
+//		}
+//	}
+//
+//	if err != nil {
+//		br.Msg = "移动失败"
+//		br.ErrMsg = "修改失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "移动成功"
+//}
+
+//// ChartClassifyMove
+//// @Title 沙盘分类移动接口
+//// @Description 沙盘分类移动接口
+//// @Success 200 {object} data_manage.MoveChartClassifyReq
+//// @router /classify/move [post]
+//func (this *SandboxController) ChartClassifyMove() {
+//	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 sandbox.MoveSandboxClassifyReq
+//	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
+//	}
+//	//判断分类是否存在
+//	sandboxClassifyInfo, err := sandbox.GetSandboxClassifyById(req.ClassifyId)
+//	if err != nil {
+//		br.Msg = "移动失败"
+//		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	updateCol := make([]string, 0)
+//
+//	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+//	if sandboxClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+//		parentChartClassifyInfo, err := sandbox.GetSandboxClassifyById(req.ParentClassifyId)
+//		if err != nil {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+//			return
+//		}
+//		sandboxClassifyInfo.ParentId = parentChartClassifyInfo.SandboxClassifyId
+//		sandboxClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+//		sandboxClassifyInfo.ModifyTime = time.Now()
+//		updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+//	} else if sandboxClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId == 0 {
+//		//改为一级分类
+//		sandboxClassifyInfo.ParentId = req.ParentClassifyId
+//		sandboxClassifyInfo.Level = 1
+//		sandboxClassifyInfo.ModifyTime = time.Now()
+//		updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+//	}
+//
+//	//如果有传入 上一个兄弟节点分类id
+//	if req.PrevClassifyId > 0 {
+//		//上一个兄弟节点
+//		prevClassify, err := sandbox.GetSandboxClassifyById(req.PrevClassifyId)
+//		if err != nil {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+//			return
+//		}
+//
+//		//如果是移动在两个兄弟节点之间
+//		if req.NextClassifyId > 0 {
+//			//下一个兄弟节点
+//			nextClassify, err := sandbox.GetSandboxClassifyById(req.NextClassifyId)
+//			if err != nil {
+//				br.Msg = "移动失败"
+//				br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+//				return
+//			}
+//			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+//			if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == sandboxClassifyInfo.Sort {
+//				//变更兄弟节点的排序
+//				updateSortStr := `sort + 2`
+//				_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, prevClassify.SandboxClassifyId, prevClassify.Sort, updateSortStr)
+//			} else {
+//				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+//				if nextClassify.Sort-prevClassify.Sort == 1 {
+//					//变更兄弟节点的排序
+//					updateSortStr := `sort + 1`
+//					_ = sandbox.UpdateSandboxClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+//				}
+//			}
+//		}
+//
+//		sandboxClassifyInfo.Sort = prevClassify.Sort + 1
+//		sandboxClassifyInfo.ModifyTime = time.Now()
+//		updateCol = append(updateCol, "Sort", "ModifyTime")
+//
+//	} else {
+//		firstClassify, err := sandbox.GetFirstSandboxClassifyByParentId(sandboxClassifyInfo.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 `
+//			_ = sandbox.UpdateSandboxClassifySortByParentId(firstClassify.ParentId, firstClassify.SandboxClassifyId-1, 0, updateSortStr)
+//		}
+//
+//		sandboxClassifyInfo.Sort = 0 //那就是排在第一位
+//		sandboxClassifyInfo.ModifyTime = time.Now()
+//		updateCol = append(updateCol, "Sort", "ModifyTime")
+//	}
+//
+//	//更新
+//	if len(updateCol) > 0 {
+//		err = sandboxClassifyInfo.Update(updateCol)
+//		if err != nil {
+//			br.Msg = "移动失败"
+//			br.ErrMsg = "修改失败,Err:" + err.Error()
+//			return
+//		}
+//	}
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "移动成功"
+//}
+
+// SandboxClassifyItems
+// @Title 获取所有沙盘分类接口-不包含沙盘
+// @Description 获取所有沙盘分类接口-不包含沙盘
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /classifyList [get]
+func (this *SandboxController) SandboxClassifyList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := new(sandbox.SandboxClassifyListResp)
+
+	rootList, err := sandbox.GetSandboxClassifyByParentId(0)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	classifyAll, err := sandbox.GetSandboxClassifyAll()
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	nodeAll := make([]*sandbox.SandboxClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		sandboxService.SandboxClassifyItemsMakeTree(this.SysUser, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 链接指标检测
+// @Description 链接指标检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /link/check [post]
+func (this *SandboxController) LinkEdbInfoCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req sandbox.SandboxLinkCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	resp := new(sandbox.SandboxLinkCheckResp)
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(req.EdbInfoIdList)
+	if err != nil {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败,err:" + err.Error()
+		return
+	}
+	for _, v := range edbInfoList {
+		resp.EdbInfoIdList = append(resp.EdbInfoIdList, v.EdbInfoId)
+	}
+
+	chartList, err := data_manage.GetChartInfoByIdList(req.ChartInfoIdList)
+	if err != nil {
+		br.Msg = `获取失败`
+		br.ErrMsg = `获取图表列表失败,ERR:` + err.Error()
+		return
+	}
+	for _, v := range chartList {
+		resp.ChartInfoIdList = append(resp.ChartInfoIdList, v.ChartInfoId)
+	}
+
+	reportList, err := models.GetSimpleReportByIds(req.ReportIdList)
+	if err != nil {
+		br.Msg = `获取失败`
+		br.ErrMsg = `获取报告列表失败,ERR:` + err.Error()
+		return
+	}
+	for _, v := range reportList {
+		resp.ReportIdList = append(resp.ReportIdList, v.Id)
+	}
+
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}

+ 1238 - 0
controllers/smart_report/smart_report.go

@@ -0,0 +1,1238 @@
+package smart_report
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/smart_report"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services"
+	smartReportService "eta/eta_api/services/smart_report"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/kgiannakakis/mp3duration/src/mp3duration"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+	"time"
+)
+
+// SmartReportController 智能研报
+type SmartReportController struct {
+	controllers.BaseAuthController
+}
+
+// Add
+// @Title 新增
+// @Description 新增
+// @Param	request	body smart_report.SmartReportAddReq true "type json string"
+// @Success 200 {object} smart_report.SmartReportItem
+// @router /add [post]
+func (this *SmartReportController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.AddType != 1 && req.AddType != 2 {
+		br.Msg = "请选择新增方式"
+		return
+	}
+	if req.ClassifyIdFirst <= 0 || req.ClassifyIdSecond <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == "" {
+		br.Msg = "请输入标题"
+		return
+	}
+
+	reportOB := new(smart_report.SmartReport)
+	stageMax, e := reportOB.GetMaxStageByClassifyId(req.ClassifyIdSecond)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取期数失败, Err: " + e.Error()
+		return
+	}
+	stageMax += 1
+
+	item := new(smart_report.SmartReport)
+	item.AddType = req.AddType
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = req.ClassifyNameFirst
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = req.ClassifyNameSecond
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.Stage = stageMax
+	item.AdminId = sysUser.AdminId
+	item.AdminRealName = sysUser.RealName
+	item.LastModifyAdminId = sysUser.AdminId
+	item.LastModifyAdminName = sysUser.RealName
+	item.State = smart_report.SmartReportStateWaitPublish
+	item.CreateTime = time.Now().Local()
+	item.ModifyTime = time.Now().Local()
+	// 继承报告
+	if req.AddType == 2 {
+		ob := new(smart_report.SmartReport)
+		cond := ` AND classify_id_first = ? AND classify_id_second = ?`
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyIdFirst, req.ClassifyIdSecond)
+		lastReport, e := ob.GetItemByCondition(cond, pars, "stage DESC")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取往期研报失败, Err: " + e.Error()
+			return
+		}
+		if lastReport != nil {
+			item.Content = lastReport.Content
+			item.ContentSub = lastReport.ContentSub
+			item.ContentStruct = lastReport.ContentStruct
+		}
+	}
+	if e = item.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增研报失败, Err: " + e.Error()
+		return
+	}
+	uniqueCode := utils.MD5(fmt.Sprint("smart_", item.SmartReportId))
+	item.ReportCode = uniqueCode
+	if e = item.Update([]string{"ReportCode"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新研报编码失败, Err: " + e.Error()
+		return
+	}
+	resp := smart_report.FormatSmartReport2Item(item)
+
+	recordItem := &models.ReportStateRecord{
+		ReportId:   item.SmartReportId,
+		ReportType: 2,
+		State:      smart_report.SmartReportStateWaitPublish,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.Data = resp
+}
+
+// Edit
+// @Title 编辑
+// @Description 编辑
+// @Param	request	body smart_report.SmartReportEditReq true "type json string"
+// @Success 200 {object} smart_report.SmartReportItem
+// @router /edit [post]
+func (this *SmartReportController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SmartReportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = "报告ID为空"
+		return
+	}
+	if req.ClassifyIdFirst <= 0 || req.ClassifyIdSecond <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == "" {
+		br.Msg = "请输入标题"
+		return
+	}
+	var subContent string
+	if req.Content != "" {
+		req.Content = html.EscapeString(req.Content)
+		// 前两个章节
+		sub, e := services.GetReportContentSub(req.Content)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "读取报告前两个章节内容失败, Err: " + e.Error()
+			return
+		}
+		subContent = html.EscapeString(sub)
+	}
+	req.ContentStruct = html.EscapeString(req.ContentStruct)
+
+	ob := new(smart_report.SmartReport)
+	item, e := ob.GetItemById(req.SmartReportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取研报失败, Err: " + e.Error()
+		return
+	}
+	if item.State == 2 {
+		br.Msg = "报告已发布, 请取消发布后编辑"
+		return
+	}
+
+	// 内容是否变更, 只有内容产生变更时, 才更新最后更新人和内容更新时间字段
+	contentModify := false
+	if item.ClassifyIdFirst != req.ClassifyIdFirst ||
+		item.ClassifyIdSecond != req.ClassifyIdSecond ||
+		item.Title != req.Title ||
+		item.Abstract != req.Abstract ||
+		item.Frequency != req.Frequency ||
+		utils.MD5(item.Content) != utils.MD5(req.Content) {
+		contentModify = true
+	}
+	cols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "Title", "Abstract", "Author",
+		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime"}
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = req.ClassifyNameFirst
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = req.ClassifyNameSecond
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.Content = req.Content
+	item.ContentSub = subContent
+	item.ContentStruct = req.ContentStruct
+	item.ModifyTime = time.Now().Local()
+	if contentModify {
+		//fmt.Println(contentModify)
+		item.LastModifyAdminId = sysUser.AdminId
+		item.LastModifyAdminName = sysUser.RealName
+		item.ContentModifyTime = time.Now().Local()
+		cols = append(cols, "LastModifyAdminId", "LastModifyAdminName", "ContentModifyTime")
+	}
+	if e := item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告失败, Err: " + e.Error()
+		return
+	}
+	resp := smart_report.FormatSmartReport2Item(item)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.Data = resp
+}
+
+// Remove
+// @Title 删除
+// @Description 删除
+// @Param	request	body smart_report.SmartReportRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /remove [post]
+func (this *SmartReportController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportRemoveReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SmartReportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = "报告ID为空"
+		return
+	}
+
+	ob := new(smart_report.SmartReport)
+	item, e := ob.GetItemById(req.SmartReportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取研报失败, Err: " + e.Error()
+		return
+	}
+	if e = item.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除报告失败, Err: " + e.Error()
+		return
+	}
+
+	// ES更新报告为未发布
+	go func() {
+		_ = smartReportService.SmartReportElasticUpsert(item.SmartReportId, 1)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 详情
+// @Description 详情
+// @Param   SmartReportId	query	int	true	"智能研报ID"
+// @Success 200 {object} smart_report.SmartReportItem
+// @router /detail [get]
+func (this *SmartReportController) Detail() {
+	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
+	}
+	reportId, _ := this.GetInt("SmartReportId")
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = "报告ID有误"
+		return
+	}
+
+	ob := new(smart_report.SmartReport)
+	item, e := ob.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取研报失败, Err: " + e.Error()
+		return
+	}
+	resp := smart_report.FormatSmartReport2Item(item)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Publish
+// @Title 发布/取消发布
+// @Description 发布/取消发布
+// @Param	request	body smart_report.SmartReportPublishReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /publish [post]
+func (this *SmartReportController) Publish() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportPublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SmartReportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = "报告ID为空"
+		return
+	}
+	if req.PublishState != smart_report.SmartReportStateWaitPublish && req.PublishState != smart_report.SmartReportStatePublished {
+		br.Msg = "参数有误"
+		return
+	}
+
+	ob := new(smart_report.SmartReport)
+	item, e := ob.GetItemById(req.SmartReportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取研报失败, Err: " + e.Error()
+		return
+	}
+
+	cols := []string{"State", "ModifyTime"}
+	item.State = req.PublishState
+	item.ModifyTime = time.Now().Local()
+	if req.PublishState == smart_report.SmartReportStatePublished {
+		cols = append(cols, "PublishTime")
+		item.PublishTime = time.Now().Local()
+
+		// 写入队列
+		var queue smart_report.Report2ImgQueueReq
+		queue.ReportType = 2
+		queue.ReportCode = item.ReportCode
+		_ = utils.Rc.LPush(utils.CACHE_CREATE_REPORT_IMGPDF_QUEUE, queue)
+	}
+	// 取消发布时同时清除掉Img和PDF的文件地址
+	if req.PublishState == smart_report.SmartReportStateWaitPublish {
+		cols = append(cols, "DetailImgUrl", "DetailPdfUrl")
+		item.DetailImgUrl = ""
+		item.DetailPdfUrl = ""
+	}
+	if e = item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新研报失败, Err: " + e.Error()
+		return
+	}
+	recordItem := &models.ReportStateRecord{
+		ReportId:   req.SmartReportId,
+		ReportType: 2,
+		State:     req.PublishState,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+	// 生成音频
+	if req.PublishState == smart_report.SmartReportStatePublished && item.VideoUrl == "" {
+		go smartReportService.SmartReportBuildVideoAndUpdate(item)
+	}
+
+	// ES更新报告
+	go func() {
+		_ = smartReportService.SmartReportElasticUpsert(item.SmartReportId, req.PublishState)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// PrePublish
+// @Title 设置定时发布
+// @Description 设置定时发布
+// @Param	request	body smart_report.SmartReportPrePublishReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /pre_publish [post]
+func (this *SmartReportController) PrePublish() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportPrePublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportId := req.SmartReportId
+	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
+	}
+	preTime, e := time.ParseInLocation(utils.FormatDateTime, req.PrePublishTime, time.Local)
+	if e != nil {
+		br.Msg = "发布时间格式错误"
+		br.ErrMsg = "发布时间格式错误,Err:" + e.Error()
+		return
+	}
+	if preTime.Before(time.Now()) {
+		br.Msg = "发布时间不允许选择过去时间"
+		return
+	}
+	if preTime.Before(time.Now().Add(2 * time.Minute)) {
+		br.Msg = "发布时间距离当前时间太近了"
+		return
+	}
+
+	reportOB := new(smart_report.SmartReport)
+	item, e := reportOB.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "获取报告失败"
+		br.ErrMsg = "获取报告失败, Err:" + e.Error()
+		return
+	}
+	if item.Content == "" {
+		br.Msg = "报告内容为空, 不可发布"
+		return
+	}
+	if item.State == 2 {
+		br.Msg = "报告已发布, 不可设置定时发布"
+		return
+	}
+
+	item.PrePublishTime = preTime
+	item.PreMsgSend = req.PreMsgSend
+	cols := []string{"PrePublishTime", "PreMsgSend"}
+	if e = item.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告预发布失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// SendMsg
+// @Title 消息推送
+// @Description 消息推送
+// @Param	request	body models.SendTemplateMsgReq true "type json string"
+// @Success 200 Ret=200 推送成功
+// @router /send_msg [post]
+func (this *SmartReportController) SendMsg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportSendMsgReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SmartReportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SmartReportId: %d", req.SmartReportId)
+		return
+	}
+
+	// 避免重复推送
+	{
+		redisKey := fmt.Sprint(utils.CACHE_SMART_REPORT_SEND_MSG, req.SmartReportId)
+		ok := utils.Rc.SetNX(redisKey, 1, time.Second*300)
+		if !ok {
+			br.Msg = "报告已推送, 请勿重复推送"
+			return
+		}
+		defer func() {
+			_ = utils.Rc.Delete(redisKey)
+		}()
+	}
+
+	reportOB := new(smart_report.SmartReport)
+	item, e := reportOB.GetItemById(req.SmartReportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+	if item.MsgIsSend == 1 {
+		br.Msg = "消息已推送,请勿重复操作"
+		return
+	}
+	if item.State != 2 {
+		br.Msg = "报告未发布, 不可推送"
+		return
+	}
+
+	item.MsgIsSend = 1
+	item.MsgSendTime = time.Now().Local()
+	item.ModifyTime = time.Now().Local()
+	if e = item.Update([]string{"MsgIsSend", "MsgSendTime", "ModifyTime"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告推送状态失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.IsAddLog = true
+}
+
+// SaveContent
+// @Title 保存草稿
+// @Description 保存草稿
+// @Param	request	body smart_report.SmartReportSaveContentReq true "type json string"
+// @Success 200 {object} smart_report.SmartReportSaveContentResp
+// @router /save_content [post]
+func (this *SmartReportController) SaveContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportSaveContentReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	resp := new(smart_report.SmartReportSaveContentResp)
+	if req.SmartReportId <= 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		br.Data = resp
+		return
+	}
+
+	reportOB := new(smart_report.SmartReport)
+	item, _ := reportOB.GetItemById(req.SmartReportId)
+	if item == nil {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		br.Data = resp
+		return
+	}
+	if item.State == smart_report.SmartReportStatePublished {
+		br.Msg = "报告已发布, 不允许编辑"
+		return
+	}
+
+	// 更新编辑状态
+	adminIdName := make(map[int]string)
+	admins, e := system.GetSysAdminList(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取系统用户列表失败, Err: " + e.Error()
+		return
+	}
+	for _, ad := range admins {
+		adminIdName[ad.AdminId] = ad.RealName
+	}
+	editing, e := smartReportService.UpdateSmartReportEditing(req.SmartReportId, 1, sysUser.AdminId, sysUser.RealName, adminIdName)
+	if e != nil {
+		br.Msg = e.Error()
+		return
+	}
+	if editing.Status == 1 {
+		br.Msg = editing.Msg
+		return
+	}
+
+	// 内容有改动
+	if req.NoChange != 1 && req.Content != "" && req.ContentStruct != "" {
+		req.Content = html.EscapeString(req.Content)
+		req.ContentStruct = html.EscapeString(req.ContentStruct)
+		sub, e := services.GetReportContentSub(req.Content)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "读取报告前两个章节内容失败, Err: " + e.Error()
+			return
+		}
+		subContent := html.EscapeString(sub)
+
+		item.Content = req.Content
+		item.ContentSub = subContent
+		item.ContentStruct = req.ContentStruct
+		item.ContentModifyTime = time.Now().Local()
+		item.LastModifyAdminId = sysUser.AdminId
+		item.LastModifyAdminName = sysUser.RealName
+		item.ModifyTime = time.Now().Local()
+		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
+		if e = item.Update(cols); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告内容失败"
+			return
+		}
+
+		go func() {
+			saveLog := new(smart_report.SmartReportSaveLog)
+			saveLog.SmartReportId = item.SmartReportId
+			saveLog.Content = item.Content
+			saveLog.ContentSub = item.ContentSub
+			saveLog.ContentStruct = item.ContentStruct
+			saveLog.AdminId = sysUser.AdminId
+			saveLog.AdminName = sysUser.RealName
+			saveLog.CreateTime = time.Now().Local()
+			_ = saveLog.Create()
+		}()
+	}
+
+	resp.SmartReportId = item.SmartReportId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// MarkEditStatus
+// @Title 标记报告编辑状态
+// @Description 标记报告编辑状态接口
+// @Param	request	body request.MarkEditEnReport true "type json string"
+// @Success 200 标记成功 ;202 标记成功
+// @router /mark_edit [post]
+func (this *SmartReportController) MarkEditStatus() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req smart_report.SmartReportMarkEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.SmartReportId <= 0 {
+		br.Msg = "缺少报告Id"
+		return
+	}
+	if req.Status <= 0 {
+		br.Msg = "标记状态异常"
+		return
+	}
+
+	// 获取系统用户列表-用于匹配编辑中的用户
+	adminIdName := make(map[int]string)
+	admins, e := system.GetSysAdminList(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取系统用户列表失败, Err: " + e.Error()
+		return
+	}
+	for _, ad := range admins {
+		adminIdName[ad.AdminId] = ad.RealName
+	}
+
+	data, e := smartReportService.UpdateSmartReportEditing(req.SmartReportId, req.Status, sysUser.AdminId, sysUser.RealName, adminIdName)
+	if e != nil {
+		br.Msg = e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "标记成功"
+	br.Data = data
+}
+
+// List
+// @Title 报告列表
+// @Description 报告列表
+// @Param   PageSize			query	int		true	"每页数据条数"
+// @Param   CurrentIndex		query	int		true	"当前页页码"
+// @Param   TimeType			query	string	false	"筛选的时间类别: publish_time-发布时间, modify_time-更新时间"
+// @Param   StartDate			query   string  false	"开始时间"
+// @Param   EndDate				query   string  false	"结束时间"
+// @Param   Frequency			query   string  false	"频度"
+// @Param   ClassifyIdFirst		query	int		false	"一级分类ID"
+// @Param   ClassifyIdSecond	query	int		false	"二级分类ID"
+// @Param   State				query	int		false	"发布状态: 1-待发布; 2-已发布"
+// @Param   Keyword				query	string	false	"搜索关键词"
+// @Success 200 {object} smart_report.SmartReportListResp
+// @router /list [get]
+func (this *SmartReportController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	type SmartReportListReq struct {
+		PageSize         int    `form:"PageSize"`
+		CurrentIndex     int    `form:"CurrentIndex"`
+		TimeType         string `form:"TimeType"`
+		StartDate        string `form:"StartDate"`
+		EndDate          string `form:"EndDate"`
+		Frequency        string `form:"Frequency"`
+		ClassifyIdFirst  int    `form:"ClassifyIdFirst"`
+		ClassifyIdSecond int    `form:"ClassifyIdSecond"`
+		State            int    `form:"State"`
+		Keyword          string `form:"Keyword"`
+	}
+	params := new(SmartReportListReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "入参解析失败, Err: " + e.Error()
+		return
+	}
+	if params.TimeType == "" {
+		params.TimeType = "publish_time"
+	}
+	if params.TimeType != "publish_time" && params.TimeType != "modify_time" {
+		br.Msg = "请选择正确的时间类型"
+		return
+	}
+	// 更新时间指的是内容更新时间
+	if params.TimeType == "modify_time" {
+		params.TimeType = "content_modify_time"
+	}
+
+	var condition string
+	var pars []interface{}
+	// 筛选项
+	{
+		keyword := strings.TrimSpace(params.Keyword)
+		if keyword != "" {
+			kw := fmt.Sprint("%", keyword, "%")
+			condition += fmt.Sprintf(` AND (title LIKE ? OR admin_real_name LIKE ? OR last_modify_admin_name LIKE ?)`)
+			pars = append(pars, kw, kw, kw)
+		}
+		if params.StartDate != "" && params.EndDate != "" {
+			st := fmt.Sprintf("%s 00:00:00", params.StartDate)
+			ed := fmt.Sprintf("%s 23:59:59", params.EndDate)
+			condition += fmt.Sprintf(` AND %s >= ? AND %s <= ?`, params.TimeType, params.TimeType)
+			pars = append(pars, st, ed)
+		}
+		if params.Frequency != "" {
+			condition += ` AND frequency = ?`
+			pars = append(pars, params.Frequency)
+		}
+		if params.ClassifyIdFirst > 0 {
+			condition += ` AND classify_id_first = ?`
+			pars = append(pars, params.ClassifyIdFirst)
+		}
+		if params.ClassifyIdSecond > 0 {
+			condition += ` AND classify_id_second = ?`
+			pars = append(pars, params.ClassifyIdSecond)
+		}
+		if params.State > 0 {
+			condition += ` AND state = ?`
+			pars = append(pars, params.State)
+		}
+	}
+
+	resp := new(smart_report.SmartReportListResp)
+	reportOB := new(smart_report.SmartReport)
+	total, e := reportOB.GetCountByCondition(condition, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取报告总数失败, Err:" + e.Error()
+		return
+	}
+	if total <= 0 {
+		page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+		resp.Paging = page
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	// 分页列表
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+
+	// 列表查询过滤掉富文本内容
+	fields := []string{
+		"smart_report_id", "report_code", "classify_id_first", "classify_name_first", "classify_id_second", "classify_name_second", "add_type",
+		"title", "abstract", "author", "frequency", "stage", "video_url", "video_name", "video_play_seconds", "video_size", "detail_img_url", "detail_pdf_url",
+		"admin_id", "admin_real_name", "state", "publish_time", "pre_publish_time", "pre_msg_send", "msg_is_send", "msg_send_time", "create_time", "modify_time",
+		"last_modify_admin_id", "last_modify_admin_name", "content_modify_time", "pv", "uv",
+	}
+	list, e := reportOB.GetPageItemsByCondition(condition, pars, fields, "", startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取报告分页列表失败, Err:" + e.Error()
+		return
+	}
+
+	// 获取系统用户列表-用于匹配编辑中的用户
+	adminIdName := make(map[int]string)
+	admins, e := system.GetSysAdminList(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取系统用户列表失败, Err: " + e.Error()
+		return
+	}
+	for _, ad := range admins {
+		adminIdName[ad.AdminId] = ad.RealName
+	}
+
+	for _, v := range list {
+		item := smart_report.FormatSmartReport2Item(v)
+		mark, e := smartReportService.UpdateSmartReportEditing(v.SmartReportId, 2, sysUser.AdminId, sysUser.RealName, adminIdName)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "查询编辑中标记失败, Err:" + e.Error()
+			return
+		}
+		if mark.Status == 0 {
+			item.CanEdit = true
+		} else {
+			item.Editor = mark.Editor
+		}
+		resp.List = append(resp.List, item)
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// DetailImg
+// @Title 生成长图
+// @Description 生成长图
+// @Param   SmartReportId	query	int	true	"智能研报ID"
+// @Success 200 {object} smart_report.SmartReportSaveContentResp
+// @router /detail_img [get]
+func (this *SmartReportController) DetailImg() {
+	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
+	}
+	reportId, _ := this.GetInt("SmartReportId", 0)
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SmartReportId: %d", reportId)
+		return
+	}
+
+	reportOB := new(smart_report.SmartReport)
+	item, e := reportOB.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+	if item.DetailImgUrl != "" {
+		br.Data = item.DetailImgUrl
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	// 写入队列
+	var queue smart_report.Report2ImgQueueReq
+	queue.ReportType = 2
+	queue.ReportCode = item.ReportCode
+	_ = utils.Rc.LPush(utils.CACHE_CREATE_REPORT_IMGPDF_QUEUE, queue)
+
+	br.Msg = "图片正在生成中, 请稍后下载"
+	return
+}
+
+// LastPublishedReport
+// @Title 上期已发布的报告
+// @Description 上期已发布的报告
+// @Param   ClassifyIdFirst		query	int	false	"一级分类ID"
+// @Param   ClassifyIdSecond	query	int	false	"二级分类ID"
+// @Success 200 {object} smart_report.SmartReportItem
+// @router /last_published_report [get]
+func (this *SmartReportController) LastPublishedReport() {
+	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
+	}
+	firstId, _ := this.GetInt("ClassifyIdFirst")
+	secondId, _ := this.GetInt("ClassifyIdSecond")
+
+	ob := new(smart_report.SmartReport)
+	cond := ` AND classify_id_first = ? AND classify_id_second = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, firstId, secondId)
+	item, e := ob.GetItemByCondition(cond, pars, "stage DESC")
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取研报失败, Err: " + e.Error()
+		return
+	}
+	resp := smart_report.FormatSmartReport2Item(item)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// VoiceUpload
+// @Title 音频上传
+// @Description 音频上传接口
+// @Param   file   query   file  true       "文件"
+// @Param   SmartReportId	query   int  true       "报告ID"
+// @Success Ret=200 上传成功
+// @router /voice_upload [post]
+func (this *SmartReportController) VoiceUpload() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	reportId, _ := this.GetInt("SmartReportId")
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SmartReportId: %d", reportId)
+		return
+	}
+	f, h, err := this.GetFile("file")
+	if err != nil {
+		br.Msg = "获取资源信息失败"
+		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		return
+	}
+	defer func() {
+		_ = f.Close()
+	}()
+
+	reportOb := new(smart_report.SmartReport)
+	item, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告信息失败, Err:" + err.Error()
+		return
+	}
+
+	ext := path.Ext(h.Filename)
+	uploadDir := utils.STATIC_DIR + "hongze/" + time.Now().Format("20060102")
+	if e = os.MkdirAll(uploadDir, 0766); e != nil {
+		br.Msg = "存储目录创建失败"
+		br.ErrMsg = "存储目录创建失败, Err:" + e.Error()
+		return
+	}
+	ossFileName := utils.GetRandStringNoSpecialChar(28) + ext
+	filePath := uploadDir + "/" + ossFileName
+	if e = this.SaveToFile("file", filePath); e != nil {
+		br.Msg = "文件保存失败"
+		br.ErrMsg = "文件保存失败, Err:" + e.Error()
+		return
+	}
+	defer func() {
+		_ = os.Remove(filePath)
+	}()
+	ossDir := "static/audio/"
+
+	resourceUrl := ``
+	//上传到阿里云 和 minio
+	if utils.ObjectStorageClient == "minio" {
+		resourceUrl, e = services.UploadMinIoToDir(ossFileName, filePath, ossDir, "")
+		if e != nil {
+			br.Msg = "文件上传失败"
+			br.ErrMsg = "文件上传失败, Err:" + e.Error()
+			return
+		}
+	} else {
+		resourceUrl, e = services.UploadAliyunToDir(ossFileName, filePath, ossDir, "")
+		if e != nil {
+			br.Msg = "文件上传失败"
+			br.ErrMsg = "文件上传失败, Err:" + e.Error()
+			return
+		}
+	}
+
+	resource := new(models.Resource)
+	resource.ResourceUrl = resourceUrl
+	resource.ResourceType = 2
+	resource.CreateTime = time.Now()
+	newId, err := models.AddResource(resource)
+	if err != nil {
+		br.Msg = "资源上传失败"
+		br.ErrMsg = "资源上传失败,Err:" + err.Error()
+		return
+	}
+	//fmt.Println(filePath)
+	playSeconds, err := mp3duration.Calculate(filePath)
+	if playSeconds <= 0 {
+		playSeconds, err = utils.GetVideoPlaySeconds(filePath)
+		if err != nil {
+			br.Msg = "获取音频时间失败"
+			br.ErrMsg = "获取音频时间失败,Err:" + err.Error()
+			return
+		}
+	}
+	createTime := item.CreateTime.Format("0102")
+	videoName := item.Title + "(" + createTime + ")"
+	fileBody, err := ioutil.ReadFile(filePath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	item.VideoUrl = resourceUrl
+	item.VideoName = videoName
+	item.VideoPlaySeconds = playSeconds
+	item.VideoSize = sizeStr
+	item.ModifyTime = time.Now().Local()
+	updateCols := []string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "ModifyTime"}
+	if e = item.Update(updateCols); e != nil {
+		br.Msg = "上传失败"
+		br.ErrMsg = "上传失败,Err:" + e.Error()
+		return
+	}
+
+	resp := new(models.ResourceResp)
+	resp.Id = newId
+	resp.ResourceUrl = resourceUrl
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 20 - 0
controllers/sys_admin.go

@@ -1261,3 +1261,23 @@ func (this *SysAdminController) ResetPass() {
 	br.IsAddLog = true
 	br.Msg = "重置密码成功"
 }
+
+
+// Add
+// @Title 用户详情信息
+// @Description 用户详情信息
+// @Param	request	body system.SysuserAddReq true "type json string"
+// @Success 200 新增成功
+// @router /sysuser/detail [get]
+func (this *SysAdminController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	br.Data = this.SysUser
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 14 - 0
controllers/sys_role.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_api/models"
+	"eta/eta_api/models/company"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
@@ -716,6 +717,19 @@ func (this *SysRoleController) SystemConfig() {
 	}
 	list = append(list, osc)
 
+	// 获取审批流设置
+	confKey := "approval_flow"
+	confTmp, e := company.GetConfigDetailByCode(confKey)
+	if e != nil {
+		br.Msg = "获取审批流配置失败"
+		br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
+		return
+	}
+	list = append(list, system.BusinessConf{
+		ConfKey: "ApprovalFlow",
+		ConfVal: confTmp.ConfigValue,
+	})
+
 	br.Data = list
 	br.Ret = 200
 	br.Success = true

+ 11 - 5
controllers/voice.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/kgiannakakis/mp3duration/src/mp3duration"
 	"github.com/rdlucklib/rdluck_tools/file"
 	"github.com/rdlucklib/rdluck_tools/http"
 	"io/ioutil"
@@ -104,11 +105,16 @@ func (this *VoiceController) Upload() {
 		br.ErrMsg = "资源上传失败,Err:" + err.Error()
 		return
 	}
-	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
+		}
 	}
 	createTime := report.CreateTime.Format("0102")
 	videoName := report.Title + "(" + createTime + ")"

+ 0 - 0
etalogs/binlog/20231023.log


+ 4 - 2
go.mod

@@ -12,8 +12,10 @@ require (
 	github.com/alibabacloud-go/tea-utils/v2 v2.0.1
 	github.com/aliyun/alibaba-cloud-sdk-go v1.61.1656
 	github.com/aliyun/aliyun-oss-go-sdk v2.2.0+incompatible
+	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
 	github.com/beego/bee/v2 v2.0.4
 	github.com/beego/beego/v2 v2.0.7
+	github.com/beevik/etree v1.2.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-sql-driver/mysql v1.7.0
 	github.com/go-xorm/xorm v0.7.9
@@ -30,7 +32,7 @@ require (
 	github.com/tealeg/xlsx v1.0.5
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.541
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.541
-	github.com/xuri/excelize/v2 v2.6.1
+	github.com/xuri/excelize/v2 v2.7.1
 	github.com/yidane/formula v0.0.0-20210902154546-0782e1736717
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 )
@@ -105,7 +107,7 @@ require (
 	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
 	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	golang.org/x/crypto v0.12.0 // indirect
-	golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect
+	golang.org/x/image v0.5.0 // indirect
 	golang.org/x/net v0.14.0 // indirect
 	golang.org/x/sys v0.11.0 // indirect
 	golang.org/x/text v0.12.0 // indirect

+ 31 - 8
go.sum

@@ -70,6 +70,8 @@ github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEq
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02 h1:o2oaBQGTzO+xNh12e7xWkphNe7H2DTiWv1ml9a2P9PQ=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20211218165449-dd623ecc2f02/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
+github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
 github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
 github.com/aws/aws-sdk-go v1.42.23/go.mod h1:gyRszuZ/icHmHAVE4gc/r+cfCmhA1AD+vqfWbgI+eHs=
 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
@@ -80,6 +82,8 @@ github.com/beego/beego/v2 v2.0.7 h1:9KNnUM40tn3pbCOFfe6SJ1oOL0oTi/oBS/C/wCEdAXA=
 github.com/beego/beego/v2 v2.0.7/go.mod h1:f0uOEkmJWgAuDTlTxUdgJzwG3PDSIf3UWF3NpMohbFE=
 github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
 github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
+github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw=
+github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -271,6 +275,7 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -364,11 +369,13 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
 github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
@@ -430,8 +437,8 @@ github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnD
 github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
 github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
 github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
-github.com/xuri/excelize/v2 v2.6.1 h1:ICBdtw803rmhLN3zfvyEGH3cwSmZv+kde7LhTDT659k=
-github.com/xuri/excelize/v2 v2.6.1/go.mod h1:tL+0m6DNwSXj/sILHbQTYsLi9IF4TW59H2EF3Yrx1AU=
+github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
+github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
 github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
 github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
 github.com/yidane/formula v0.0.0-20210902154546-0782e1736717 h1:9CTJJpdISGxMAELfVlprj5kZEsJEaNAWiobv8ZAd72U=
@@ -440,6 +447,7 @@ github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
@@ -452,20 +460,23 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
-golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
+golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -482,12 +493,15 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
 golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -500,6 +514,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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/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=
@@ -523,20 +539,26 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+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.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -554,6 +576,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -616,7 +640,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 355 - 0
models/data_manage/chart_framework.go

@@ -0,0 +1,355 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// ChartFramework 图库框架表
+type ChartFramework struct {
+	ChartFrameworkId int       `orm:"column(chart_framework_id);pk"`
+	FrameworkCode    string    `description:"框架唯一编码"`
+	FrameworkName    string    `description:"框架名称"`
+	FrameworkImg     string    `description:"框架图片"`
+	FrameworkContent string    `description:"框架内容"`
+	IsPublic         int       `description:"是否公开:0-私有;1-公开"`
+	PublicTime       time.Time `description:"公开时间"`
+	Sort             int       `description:"排序"`
+	AdminId          int       `description:"创建人ID"`
+	AdminName        string    `description:"创建人姓名"`
+	CreateTime       time.Time `description:"创建时间"`
+	ModifyTime       time.Time `description:"更新时间"`
+}
+
+func (m *ChartFramework) TableName() string {
+	return "chart_framework"
+}
+
+func (m *ChartFramework) PrimaryId() string {
+	return ChartFrameworkColumns.ChartFrameworkId
+}
+
+var ChartFrameworkColumns = struct {
+	ChartFrameworkId string
+	FrameworkCode    string
+	FrameworkName    string
+	FrameworkImg     string
+	FrameworkContent string
+	IsPublic         string
+	PublicTime       string
+	Sort             string
+	AdminId          string
+	AdminName        string
+	CreateTime       string
+	ModifyTime       string
+}{
+	ChartFrameworkId: "chart_framework_id",
+	FrameworkCode:    "framework_code",
+	FrameworkName:    "framework_name",
+	FrameworkImg:     "framework_img",
+	FrameworkContent: "framework_content",
+	IsPublic:         "is_public",
+	PublicTime:       "public_time",
+	Sort:             "sort",
+	AdminId:          "admin_id",
+	AdminName:        "admin_name",
+	CreateTime:       "create_time",
+	ModifyTime:       "modify_time",
+}
+
+func (m *ChartFramework) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ChartFrameworkId = int(id)
+	return
+}
+
+func (m *ChartFramework) CreateMulti(items []*ChartFramework) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ChartFramework) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ChartFramework) Del() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ChartFrameworkId).Exec()
+	return
+}
+
+func (m *ChartFramework) GetItemById(id int) (item *ChartFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ChartFramework) GetItemByCondition(condition string, pars []interface{}) (item *ChartFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *ChartFramework) 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 *ChartFramework) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ChartFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *ChartFramework) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ChartFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func (m *ChartFramework) CreateFrameworkAndNodes(item *ChartFramework, nodes []*ChartFrameworkNode) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %s", e.Error())
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	id, e := tx.Insert(item)
+	if e != nil {
+		err = fmt.Errorf("insert framework err: %s", e.Error())
+		return
+	}
+	newId := int(id)
+	item.ChartFrameworkId = newId
+
+	if len(nodes) > 0 {
+		for _, n := range nodes {
+			n.ChartFrameworkId = newId
+		}
+		_, e = tx.InsertMulti(len(nodes), nodes)
+		if e != nil {
+			err = fmt.Errorf("insert multi nodes err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+func (m *ChartFramework) EditFrameworkAndNodes(item *ChartFramework, updateCols []string, nodes []*ChartFrameworkNode) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %s", e.Error())
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	_, e = tx.Update(item, updateCols...)
+	if e != nil {
+		err = fmt.Errorf("framework update err: %s", e.Error())
+		return
+	}
+
+	sql := `DELETE FROM chart_framework_node WHERE chart_framework_id = ?`
+	_, e = tx.Raw(sql, item.ChartFrameworkId).Exec()
+	if e != nil {
+		err = fmt.Errorf("clear nodes err: %s", e.Error())
+		return
+	}
+	if len(nodes) > 0 {
+		_, e = tx.InsertMulti(len(nodes), nodes)
+		if e != nil {
+			err = fmt.Errorf("insert multi nodes err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+func (m *ChartFramework) RemoveFrameworkAndNodes(frameworkId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %s", e.Error())
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, e = tx.Raw(sql, frameworkId).Exec()
+	if e != nil {
+		err = fmt.Errorf("delete framework err: %s", e.Error())
+		return
+	}
+
+	sql = `DELETE FROM chart_framework_node WHERE chart_framework_id = ?`
+	_, e = tx.Raw(sql, frameworkId).Exec()
+	if e != nil {
+		err = fmt.Errorf("clear nodes err: %s", e.Error())
+		return
+	}
+	return
+}
+
+// ChartFrameworkAddReq 图库框架新增请求体
+type ChartFrameworkAddReq struct {
+	FrameworkName    string                  `description:"框架名称"`
+	FrameworkImg     string                  `description:"框架图片"`
+	FrameworkContent string                  `description:"框架内容"`
+	Nodes            []ChartFrameworkNodeReq `description:"框架节点"`
+}
+
+// ChartFrameworkNodeReq 图库框架节点请求体
+type ChartFrameworkNodeReq struct {
+	MyChartClassifyId int    `description:"我的图表分类ID"`
+	NodeName          string `description:"节点名称"`
+}
+
+// ChartFrameworkEditReq 图库框架编辑请求体
+type ChartFrameworkEditReq struct {
+	ChartFrameworkId int `description:"图库框架ID"`
+	ChartFrameworkAddReq
+}
+
+// ChartFrameworkRemoveReq 图库框架编辑请求体
+type ChartFrameworkRemoveReq struct {
+	ChartFrameworkId int `description:"图库框架ID"`
+}
+
+// ChartFrameworkRenameReq 图库框架重命名请求体
+type ChartFrameworkRenameReq struct {
+	ChartFrameworkId int    `description:"图库框架ID"`
+	FrameworkName    string `description:"框架名称"`
+}
+
+// ChartFrameworkEditPublicReq 图库框架编辑公开请求体
+type ChartFrameworkEditPublicReq struct {
+	ChartFrameworkId int `description:"图库框架ID"`
+	IsPublic         int `description:"0-隐藏公开; 1-公开"`
+}
+
+// ChartFrameworkMoveReq 图库框架移动排序请求体
+type ChartFrameworkMoveReq struct {
+	ChartFrameworkId     int `description:"图库框架ID"`
+	PrevChartFrameworkId int `description:"上一个框架ID"`
+	NextChartFrameworkId int `description:"下一个框架ID"`
+}
+
+// UpdateChartFrameworkSort 更新我的图库框架排序
+func UpdateChartFrameworkSort(adminId, frameworkId, current int, updates string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`UPDATE chart_framework SET sort = %s WHERE admin_id = ? and sort > ?`, updates)
+	if frameworkId > 0 {
+		sql += ` OR (chart_framework_id > ` + fmt.Sprint(frameworkId) + ` AND sort = ` + fmt.Sprint(current) + `)`
+	}
+	_, err = o.Raw(sql, adminId, current).Exec()
+	return
+}
+
+// GetFirstChartFramework 获取我的图库框架排首位的数据
+func GetFirstChartFramework(adminId int) (item *ChartFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_framework WHERE admin_id = ? ORDER BY sort ASC,chart_framework_id ASC LIMIT 1`
+	err = o.Raw(sql, adminId).QueryRow(&item)
+	return
+}
+
+// ChartFrameworkItem 图库框架表信息
+type ChartFrameworkItem struct {
+	ChartFrameworkId int    `description:"框架ID"`
+	FrameworkCode    string `description:"框架唯一编码"`
+	FrameworkName    string `description:"框架名称"`
+	FrameworkImg     string `description:"框架图片"`
+	FrameworkContent string `description:"框架内容"`
+	IsPublic         int    `description:"是否公开:0-私有;1-公开"`
+	PublicTime       string `description:"公开时间"`
+	Sort             int    `description:"排序"`
+	AdminId          int    `description:"创建人ID"`
+	AdminName        string `description:"创建人姓名"`
+	CreateTime       string `description:"创建时间"`
+	ModifyTime       string `description:"更新时间"`
+}
+
+// FormatChartFramework2Item 格式化框架信息
+func FormatChartFramework2Item(origin *ChartFramework) (item *ChartFrameworkItem) {
+	if origin == nil {
+		return
+	}
+	item = new(ChartFrameworkItem)
+	item.ChartFrameworkId = origin.ChartFrameworkId
+	item.FrameworkCode = origin.FrameworkCode
+	item.FrameworkName = origin.FrameworkName
+	item.FrameworkImg = origin.FrameworkImg
+	item.FrameworkContent = origin.FrameworkContent
+	item.IsPublic = origin.IsPublic
+	item.PublicTime = utils.TimeTransferString(utils.FormatDateTime, origin.PublicTime)
+	item.Sort = origin.Sort
+	item.AdminId = origin.AdminId
+	item.AdminName = origin.AdminName
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// ChartFrameworkPublicMenuItem 公开框架目录
+type ChartFrameworkPublicMenuItem struct {
+	AdminId    int                   `description:"创建人ID"`
+	MenuName   string                `description:"目录名称"`
+	Frameworks []*ChartFrameworkItem `description:"框架列表"`
+}

+ 135 - 0
models/data_manage/chart_framework_node.go

@@ -0,0 +1,135 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// ChartFrameworkNode 图库框架节点表
+type ChartFrameworkNode struct {
+	ChartFrameworkNodeId int       `orm:"column(chart_framework_node_id);pk"`
+	ChartFrameworkId     int       `description:"框架ID"`
+	FrameworkName        string    `description:"框架名称"`
+	NodeName             string    `description:"节点名称"`
+	MyChartClassifyId    int       `description:"我的图表分类ID"`
+	CreateTime           time.Time `description:"创建时间"`
+}
+
+func (m *ChartFrameworkNode) TableName() string {
+	return "chart_framework_node"
+}
+
+func (m *ChartFrameworkNode) PrimaryId() string {
+	return "chart_framework_node_id"
+}
+
+func (m *ChartFrameworkNode) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ChartFrameworkNodeId = int(id)
+	return
+}
+
+func (m *ChartFrameworkNode) CreateMulti(items []*ChartFrameworkNode) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ChartFrameworkNode) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ChartFrameworkNode) Del() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ChartFrameworkNodeId).Exec()
+	return
+}
+
+func (m *ChartFrameworkNode) GetItemById(id int) (item *ChartFrameworkNode, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ChartFrameworkNode) GetItemByCondition(condition string, pars []interface{}) (item *ChartFrameworkNode, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *ChartFrameworkNode) 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 *ChartFrameworkNode) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ChartFrameworkNode, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *ChartFrameworkNode) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ChartFrameworkNode, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ChartFrameworkNodeItem 图库框架节点信息
+type ChartFrameworkNodeItem struct {
+	ChartFrameworkNodeId int
+	ChartFrameworkId     int    `description:"框架ID"`
+	FrameworkName        string `description:"框架名称"`
+	NodeName             string `description:"节点名称"`
+	MyChartClassifyId    int    `description:"我的图表分类ID"`
+	CreateTime           string `description:"创建时间"`
+}
+
+// FormatChartFrameworkNode2Item 格式化框架节点信息
+func FormatChartFrameworkNode2Item(origin *ChartFrameworkNode) (item *ChartFrameworkNodeItem) {
+	if origin == nil {
+		return
+	}
+	item = new(ChartFrameworkNodeItem)
+	item.ChartFrameworkNodeId = origin.ChartFrameworkNodeId
+	item.ChartFrameworkId = origin.ChartFrameworkId
+	item.FrameworkName = origin.FrameworkName
+	item.NodeName = origin.NodeName
+	item.MyChartClassifyId = origin.MyChartClassifyId
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	return
+}

+ 3 - 3
models/data_manage/edb_classify.go

@@ -63,10 +63,10 @@ func GetEdbClassifyCountById(classifyId int) (count int, err error) {
 	return
 }
 
-func EditEdbClassify(classifyId int, classifyName string) (err error) {
+func EditEdbClassify(classifyId int, classifyName string, userId int, userRealName string) (err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := `UPDATE edb_classify SET classify_name=?,modify_time=NOW() WHERE classify_id=? `
-	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	sql := `UPDATE edb_classify SET classify_name=?,modify_time=NOW(), last_modify_user_id = ?, last_modify_user_real_name = ? WHERE classify_id=? `
+	_, err = o.Raw(sql, classifyName, userId, userRealName, classifyId).Exec()
 	return
 }
 

+ 5 - 2
models/data_manage/edb_data_base.go

@@ -7,7 +7,7 @@ import (
 	"time"
 )
 
-// 指标数据->存储表
+// GetEdbDataTableName 指标数据->存储表
 func GetEdbDataTableName(source int) (tableName string) {
 	switch source {
 	case utils.DATA_SOURCE_THS:
@@ -157,7 +157,10 @@ func GetEdbDataTableName(source int) (tableName string) {
 	case utils.DATA_SOURCE_CALCULATE_RJZ: //日均值->75
 		tableName = "edb_data_calculate_rjz"
 	default:
-		tableName = ""
+		edbSource := EdbSourceIdMap[source]
+		if edbSource != nil {
+			tableName = edbSource.TableName
+		}
 	}
 	return
 }

+ 1 - 0
models/data_manage/edb_info.go

@@ -1776,6 +1776,7 @@ type TraceEdbInfoResp struct {
 	UniqueCode  string             `description:"唯一编码"`
 	ClassifyId  int                `description:"分类ID"`
 	Child       []TraceEdbInfoResp `description:"下级来源"`
+	EdbInfo     *EdbInfo           `description:"指标信息" json:"-"`
 }
 
 // BeforeAndAfterDateDataResp 前后几期数据

+ 3 - 1
models/data_manage/edb_info_calculate.go

@@ -199,9 +199,10 @@ type EdbInfoCalculateBatchSaveReqByEdbLib struct {
 	MoveType         int              `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency    string           `description:"移动频度:天/周/月/季/年"`
 	Calendar         string           `description:"公历/农历"`
+	Data             interface{}      `description:"数据列"`
 }
 
-// EdbInfoCalculateBatchEditReq 编辑计算指标的请求参数
+// EdbInfoCalculateBatchEditReqByEdbLib 编辑计算指标的请求参数
 type EdbInfoCalculateBatchEditReqByEdbLib struct {
 	EdbName       string `description:"指标名称"`
 	Frequency     string `description:"频度"`
@@ -215,6 +216,7 @@ type EdbInfoCalculateBatchEditReqByEdbLib struct {
 	MoveFrequency string `description:"移动频度:天/周/月/季/年"`
 	Calendar      string `description:"公历/农历"`
 	EdbInfoIdArr  []EdbInfoFromTag
+	Data          interface{} `description:"数据列"`
 }
 
 func GetEdbInfoCalculateMap(edbInfoId, source int) (list []*EdbInfo, err error) {

+ 37 - 5
models/data_manage/edb_source.go

@@ -1,20 +1,31 @@
 package data_manage
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"strings"
 )
 
+var (
+	EdbSourceIdMap map[int]*EdbSource // 指标来源
+)
+
 // EdbSource 指标来源表
 type EdbSource struct {
-	EdbSourceId int    `orm:"column(edb_source_id);pk"`
-	SourceName  string `description:"指标来源名称"`
-	TableName   string `description:"数据表名"`
-	IsBase      int    `description:"是否为基础指标: 0-否; 1-是"`
+	EdbSourceId      int    `orm:"column(edb_source_id);pk"`
+	SourceName       string `description:"指标来源名称"`
+	TableName        string `description:"数据表名"`
+	EdbAddMethod     string `description:"指标新增接口"`
+	EdbRefreshMethod string `description:"指标刷新接口"`
+	IsBase           int    `description:"是否为基础指标: 0-否; 1-是"`
+	FromBridge       int    `description:"是否来源于桥接服务: 0-否; 1-是"`
+	BridgeFlag       string `description:"桥接服务对象标识"`
+	SourceExtend     string `description:"扩展字段做查询用"`
+	EdbCodeRequired  int    `description:"指标编码是否必填: 0-否; 1-是"`
 }
 
-// GetEdbSourceItemsByCondition 获取指标来源
+// GetEdbSourceItemsByCondition 获取指标来源列表
 func GetEdbSourceItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*EdbSource, err error) {
 	o := orm.NewOrmUsingDB("data")
 	fields := strings.Join(fieldArr, ",")
@@ -38,3 +49,24 @@ type EdbSourceChild struct {
 	IsBase      int    `description:"是否为基础指标: 2-否; 1-是"`
 	Child       []EdbSourceChild
 }
+
+// GetEdbSourceItemByCondition 获取指标来源
+func GetEdbSourceItemByCondition(condition string, pars []interface{}) (item *EdbSource, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM edb_source WHERE 1=1 %s`, condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// InitEdbSourceVar 初始化时加载指标来源对应信息, 避免循环中查库, 注意edb_source表修改table_name的话需要重启服务
+func InitEdbSourceVar() {
+	EdbSourceIdMap = make(map[int]*EdbSource)
+	sources, e := GetEdbSourceItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		utils.FileLog.Info("init source table err: %s", e.Error())
+		return
+	}
+	for _, v := range sources {
+		EdbSourceIdMap[v.EdbSourceId] = v
+	}
+}

+ 13 - 7
models/data_manage/excel_classify.go → models/data_manage/excel/excel_classify.go

@@ -1,4 +1,4 @@
-package data_manage
+package excel
 
 import (
 	"fmt"
@@ -9,6 +9,7 @@ import (
 // ExcelClassify excel表格分类
 type ExcelClassify struct {
 	ExcelClassifyId   int       `orm:"column(excel_classify_id);pk"`
+	Source            int       `description:"表格来源,1:excel插件的表格,2:自定义表格,3:混合表格,4:自定义分析,默认:1"`
 	ExcelClassifyName string    `description:"分类名称"`
 	ParentId          int       `description:"父级id"`
 	SysUserId         int       `description:"创建人id"`
@@ -25,14 +26,19 @@ type ExcelClassify struct {
 func AddExcelClassify(item *ExcelClassify) (lastId int64, err error) {
 	o := orm.NewOrmUsingDB("data")
 	lastId, err = o.Insert(item)
+	if err != nil {
+		return
+	}
+	item.ExcelClassifyId = int(lastId)
+
 	return
 }
 
 // GetExcelClassifyCount 获取同级分类下存在同名分类的数量
-func GetExcelClassifyCount(ExcelClassifyName string, parentId int) (count int, err error) {
+func GetExcelClassifyCount(ExcelClassifyName string, parentId, source int) (count int, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := `SELECT COUNT(1) AS count FROM excel_classify WHERE parent_id=? AND excel_classify_name=? AND is_delete=0 `
-	err = o.Raw(sql, parentId, ExcelClassifyName).QueryRow(&count)
+	sql := `SELECT COUNT(1) AS count FROM excel_classify WHERE parent_id=? AND source = ? AND excel_classify_name=? AND is_delete=0 `
+	err = o.Raw(sql, parentId, source, ExcelClassifyName).QueryRow(&count)
 	return
 }
 
@@ -43,10 +49,10 @@ func GetExcelClassifyById(classifyId int) (item *ExcelClassify, err error) {
 	return
 }
 
-func GetExcelClassifyByParentId(parentId int) (items []*ExcelClassifyItems, err error) {
+func GetExcelClassifyByParentId(parentId, source int) (items []*ExcelClassifyItems, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := ` SELECT * FROM excel_classify WHERE parent_id=? AND is_delete=0 order by sort asc,excel_classify_id asc`
-	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	sql := ` SELECT * FROM excel_classify WHERE parent_id=? AND source = ? AND is_delete=0 order by sort asc,excel_classify_id asc`
+	_, err = o.Raw(sql, parentId, source).QueryRows(&items)
 	return
 }
 

+ 1 - 1
models/data_manage/excel_draft.go → models/data_manage/excel/excel_draft.go

@@ -1,4 +1,4 @@
-package data_manage
+package excel
 
 import (
 	"github.com/beego/beego/v2/client/orm"

+ 112 - 0
models/data_manage/excel/excel_edb_mapping.go

@@ -0,0 +1,112 @@
+package excel
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ExcelEdbMapping excel与指标的关系表
+type ExcelEdbMapping struct {
+	ExcelEdbMappingId int       `orm:"column(excel_edb_mapping_id);pk"`
+	ExcelInfoId       int       `description:"excel的id"`
+	Source            int       `description:"表格来源,1:excel插件的表格,2:自定义表格,3:混合表格,4:自定义分析,默认:1"`
+	EdbInfoId         int       `description:"计算指标id"`
+	CreateTime        time.Time `description:"创建时间"`
+	ModifyTime        time.Time `description:"修改时间"`
+}
+
+// AddExcelEdbMappingMulti 批量添加excel与指标的关系
+func AddExcelEdbMappingMulti(items []*ExcelEdbMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// Add 添加excel与指标的关系
+func (e *ExcelEdbMapping) Add() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Insert(e)
+	return
+}
+
+// GetExcelEdbMappingByEdbInfoId 根据指标id获取配置关系
+func GetExcelEdbMappingByEdbInfoId(edbInfoId int) (item *ExcelEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM excel_edb_mapping WHERE 1=1 AND edb_info_id = ? `
+
+	err = o.Raw(sql, edbInfoId).QueryRow(&item)
+	return
+}
+
+// GetExcelEdbMappingByExcelInfoId 根据excel的id获取配置关系
+func GetExcelEdbMappingByExcelInfoId(excelInfoId int) (items []*ExcelEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM excel_edb_mapping AS a 
+           join edb_info as b on a.edb_info_id = b.edb_info_id
+           WHERE 1=1 AND a.excel_info_id = ? `
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&items)
+
+	return
+}
+
+type ExcelEdbMappingItem struct {
+	EdbInfoId        int    `description:"指标id"`
+	UniqueCode       string `description:"唯一编码"`
+	EdbName          string `description:"指标名称"`
+	ClassifyId       int    `description:"分类id"`
+	Frequency        string `description:"频度"`
+	Unit             string `description:"单位"`
+	CalculateFormula string `json:"-"`
+	DateSequenceStr  string `description:"日期序列公式"`
+	DataSequenceStr  string `description:"数据序列公式"`
+}
+
+// CalculateFormula 计算公式
+type CalculateFormula struct {
+	DateSequenceStr string `json:"DateSequenceStr"`
+	DataSequenceStr string `json:"DataSequenceStr"`
+}
+
+// GetAllExcelEdbMappingItemByExcelInfoId 根据品种id获取所有的指标结果集
+func GetAllExcelEdbMappingItemByExcelInfoId(excelInfoId int) (items []*ExcelEdbMappingItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.edb_info_id,a.unique_code,a.edb_name,a.classify_id,a.frequency,a.unit,calculate_formula FROM edb_info AS a 
+         JOIN excel_edb_mapping AS b ON a.edb_info_id=b.edb_info_id 
+         WHERE b.excel_info_id = ? ORDER BY b.excel_edb_mapping_id ASC `
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&items)
+	return
+}
+
+// GetNoCustomAnalysisExcelEdbMappingCount 根据指标id获取非自定义分析的关联关系
+func GetNoCustomAnalysisExcelEdbMappingCount(edbInfoId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM excel_edb_mapping a 
+                          join excel_info b on a.excel_info_id=b.excel_info_id
+                          WHERE edb_info_id=? AND a.source != 4 AND b.is_delete = 0`
+	err = o.Raw(sql, edbInfoId).QueryRow(&count)
+	return
+}
+
+// GetAllExcelEdbMappingByExcelInfoId 根据excel的id获取所有的指标
+func GetAllExcelEdbMappingByExcelInfoId(excelInfoId int) (items []*ExcelEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.* FROM  excel_edb_mapping a
+         WHERE a.excel_info_id = ? ORDER BY a.excel_edb_mapping_id ASC `
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&items)
+	return
+}
+
+// DeleteCustomAnalysisExcelEdbMappingByEdbInfoId
+// @Description: 根据指标id删除与自定义分析表格的关系
+// @author: Roc
+// @datetime2023-11-02 13:20:02
+// @param excelInfoId int
+// @return err error
+func DeleteCustomAnalysisExcelEdbMappingByEdbInfoId(excelInfoId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM excel_edb_mapping WHERE source = ? AND edb_info_id = ? LIMIT 1`
+	_, err = o.Raw(sql, utils.CUSTOM_ANALYSIS_TABLE, excelInfoId).Exec()
+
+	return
+}

+ 247 - 28
models/data_manage/excel_info.go → models/data_manage/excel/excel_info.go

@@ -1,4 +1,4 @@
-package data_manage
+package excel
 
 import (
 	"fmt"
@@ -9,7 +9,7 @@ import (
 // ExcelInfo excel表格详情表
 type ExcelInfo struct {
 	ExcelInfoId     int       `orm:"column(excel_info_id);pk"`
-	Source          int       `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
+	Source          int       `description:"表格来源,1:excel插件的表格,2:自定义表格,3:混合表格,4:自定义分析,默认:1"`
 	ExcelType       int       `description:"表格类型,1:指标列,2:日期列,默认:1"`
 	ExcelName       string    `description:"表格名称"`
 	UniqueCode      string    `description:"表格唯一编码"`
@@ -49,22 +49,72 @@ type MyExcelInfoList struct {
 }
 
 // AddExcelInfo 新增表格
-func AddExcelInfo(excelInfo *ExcelInfo) (err error) {
-	o := orm.NewOrmUsingDB("data")
+func AddExcelInfo(excelInfo *ExcelInfo, excelEdbMappingList []*ExcelEdbMapping) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+		} else {
+			_ = o.Commit()
+		}
+	}()
 	// 表格信息入库
 	lastId, err := o.Insert(excelInfo)
 	if err != nil {
 		return
 	}
 	excelInfo.ExcelInfoId = int(lastId)
+
+	// excel与指标的关联关系
+	dataNum := len(excelEdbMappingList)
+	if dataNum > 0 {
+		for k, v := range excelEdbMappingList {
+			v.ExcelInfoId = excelInfo.ExcelInfoId
+			excelEdbMappingList[k] = v
+		}
+		_, err = o.InsertMulti(dataNum, excelEdbMappingList)
+	}
+
 	return
 }
 
 // EditExcelInfo 编辑表格
-func EditExcelInfo(excelInfo *ExcelInfo, updateExcelInfoParams []string) (err error) {
-	o := orm.NewOrmUsingDB("data")
+func EditExcelInfo(excelInfo *ExcelInfo, updateExcelInfoParams []string, excelEdbMappingList []*ExcelEdbMapping) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+		} else {
+			_ = o.Commit()
+		}
+	}()
+
 	// ETA表格信息变更
 	_, err = o.Update(excelInfo, updateExcelInfoParams...)
+	if err != nil {
+		return
+	}
+
+	// 删除关系表
+	sql := `DELETE FROM excel_edb_mapping WHERE excel_info_id=? `
+	_, err = o.Raw(sql, excelInfo.ExcelInfoId).Exec()
+
+	// excel与指标的关联关系
+	dataNum := len(excelEdbMappingList)
+	if dataNum > 0 {
+		for k, v := range excelEdbMappingList {
+			v.ExcelInfoId = excelInfo.ExcelInfoId
+			excelEdbMappingList[k] = v
+		}
+		_, err = o.InsertMulti(dataNum, excelEdbMappingList)
+	}
+
 	return
 }
 
@@ -79,12 +129,28 @@ func GetExcelInfoAll() (items []*ExcelClassifyItems, err error) {
 }
 
 // GetNoContentExcelInfoAll 获取不含content的表格列表 用于分类展示
-func GetNoContentExcelInfoAll() (items []*ExcelClassifyItems, err error) {
+func GetNoContentExcelInfoAll(source, userId int) (items []*ExcelClassifyItems, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT excel_info_id,excel_classify_id,excel_name AS excel_classify_name,
              unique_code,sys_user_id,sys_user_real_name
-            FROM excel_info where is_delete=0 ORDER BY sort asc,create_time desc `
-	_, err = o.Raw(sql).QueryRows(&items)
+            FROM excel_info where is_delete=0 AND source = ?  `
+
+	pars := []interface{}{source}
+
+	if userId > 0 {
+		sql += ` AND sys_user_id = ? `
+		pars = append(pars, userId)
+	}
+	sql += `  ORDER BY sort asc,create_time desc `
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+// GetAllExcelInfoBySource 根据来源获取包含content的表格列表
+func GetAllExcelInfoBySource(source int) (items []*ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info where is_delete=0  AND source = ?  ORDER BY sort asc,create_time desc `
+	_, err = o.Raw(sql, source).QueryRows(&items)
 	return
 }
 
@@ -136,14 +202,15 @@ func GetNextExcelInfoByCondition(condition string, pars []interface{}) (item *Ex
 }
 
 // GetNextExcelInfo 根据分类id获取下一个excel表格
-func GetNextExcelInfo(classifyId, classifySort int) (item *ExcelInfo, err error) {
+func GetNextExcelInfo(classifyId, classifySort, source int) (item *ExcelInfo, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT b.* FROM excel_classify AS a
 			INNER JOIN excel_info AS b ON a.excel_classify_id=b.excel_classify_id
 			WHERE (a.sort>? OR (a.sort=? and a.excel_classify_id>?) ) AND a.is_delete=0 AND b.is_delete=0
+			AND a.source = ? AND b.source = ? 
 			ORDER BY a.sort ASC,b.sort asc,b.create_time desc
 			LIMIT 1 `
-	err = o.Raw(sql, classifySort, classifySort, classifyId).QueryRow(&item)
+	err = o.Raw(sql, classifySort, classifySort, classifyId, source, source).QueryRow(&item)
 	return
 }
 
@@ -180,10 +247,11 @@ func GetFirstExcelInfoByClassifyId(classifyId int) (item *ExcelInfo, err error)
 // UpdateExcelInfoSortByClassifyId 根据表格id更新排序
 func UpdateExcelInfoSortByClassifyId(classifyId, nowSort, prevExcelInfoId int, updateSort string) (err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := ` update excel_info set sort = ` + updateSort + ` WHERE excel_classify_id=? and sort > ? AND is_delete=0 `
+	sql := ` update excel_info set sort = ` + updateSort + ` WHERE excel_classify_id=? AND is_delete=0 AND ( sort > ? `
 	if prevExcelInfoId > 0 {
-		sql += ` or (excel_info_id > ` + fmt.Sprint(prevExcelInfoId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+		sql += ` or (excel_info_id < ` + fmt.Sprint(prevExcelInfoId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
 	}
+	sql += `)`
 	_, err = o.Raw(sql, classifyId, nowSort).Exec()
 	return
 }
@@ -252,24 +320,10 @@ func GetExcelListCountByCondition(condition string, pars []interface{}) (count i
 	return
 }
 
-// GetMyExcelListByAdminId 根据操作人id获取表格列表
-func GetMyExcelListByAdminId(adminId int) (item []*MyChartView, err error) {
-	o := orm.NewOrmUsingDB("data")
-	//sql := ` SELECT * FROM my_chart WHERE 1=1 AND admin_id=? `
-
-	sql := ` SELECT a.*,GROUP_CONCAT(c.my_chart_classify_id SEPARATOR ',') AS my_chart_classify_id FROM my_chart AS a
-			LEFT JOIN  my_chart_classify_mapping AS b ON a.my_chart_id=b.my_chart_id AND a.admin_id=b.admin_id
-			LEFT JOIN my_chart_classify AS c ON b.my_chart_classify_id=c.my_chart_classify_id AND b.admin_id=c.admin_id
-			WHERE 1=1 AND a.admin_id=? AND a.source=2
-			GROUP BY a.chart_info_id `
-	_, err = o.Raw(sql, adminId).QueryRows(&item)
-	return
-}
-
 // GetExcelViewInfoByExcelInfoId 根据excelInfoId 获取ETA表格详情
 func GetExcelViewInfoByExcelInfoId(excelInfoId int) (item *MyExcelInfoList, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := ` SELECT * FROM excel_info WHERE excel_info_id = ? AND is_delete=0 `
+	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 FROM excel_info WHERE excel_info_id = ? AND is_delete=0 `
 	err = o.Raw(sql, excelInfoId).QueryRow(&item)
 	return
 }
@@ -281,3 +335,168 @@ func GetExcelInfoCountByClassifyId(classifyId int) (total int64, err error) {
 	err = o.Raw(sql, classifyId).QueryRow(&total)
 	return
 }
+
+// UpdateExcelInfoClassifyId 更改表格分类
+func UpdateExcelInfoClassifyId(classifyId, excelInfoId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update excel_info set excel_classify_id = ? WHERE excel_info_id=? `
+	_, err = o.Raw(sql, classifyId, excelInfoId).Exec()
+
+	return
+}
+
+// GetNoContentExcelInfoByName 根据名称 获取eta表格详情
+func GetNoContentExcelInfoByName(excelName string, source int) (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 
+ FROM excel_info WHERE excel_name = ? AND source = ? AND is_delete=0 `
+	err = o.Raw(sql, excelName, source).QueryRow(&item)
+
+	return
+}
+
+// GetNoContentExcelInfoByUniqueCode 根据unique_code来获取excel表格详情
+func GetNoContentExcelInfoByUniqueCode(uniqueCode string) (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 
+ FROM excel_info WHERE unique_code=? AND is_delete=0 `
+	err = o.Raw(sql, uniqueCode).QueryRow(&item)
+	return
+}
+
+// AddExcelInfoAndSheet 新增excel
+func AddExcelInfoAndSheet(excelInfo *ExcelInfo, sheetParamsList []AddExcelSheetParams) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+		} else {
+			_ = o.Commit()
+		}
+	}()
+
+	// 表格信息入库
+	lastId, err := o.Insert(excelInfo)
+	if err != nil {
+		return
+	}
+	excelInfo.ExcelInfoId = int(lastId)
+
+	// sheet信息入库
+	for _, sheetInfo := range sheetParamsList {
+		dataNum := len(sheetInfo.DataList)
+
+		//sheet信息入库
+		excelSheetInfo := &ExcelSheet{
+			ExcelSheetId: 0,
+			ExcelInfoId:  excelInfo.ExcelInfoId,
+			SheetName:    sheetInfo.SheetName,
+			PageNum:      dataNum,
+			Index:        sheetInfo.Index,
+			Sort:         sheetInfo.Sort,
+			Config:       sheetInfo.Config,
+			CalcChain:    sheetInfo.CalcChain,
+			ModifyTime:   time.Now(),
+			CreateTime:   time.Now(),
+		}
+		sheetId, tmpErr := o.Insert(excelSheetInfo)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		excelSheetInfo.ExcelSheetId = int(sheetId)
+
+		// data信息入库
+		if dataNum > 0 {
+			for k, _ := range sheetInfo.DataList {
+				sheetInfo.DataList[k].ExcelSheetId = excelSheetInfo.ExcelSheetId
+				sheetInfo.DataList[k].ExcelInfoId = excelSheetInfo.ExcelInfoId
+			}
+			_, tmpErr = o.InsertMulti(dataNum, sheetInfo.DataList)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+		}
+	}
+
+	return
+}
+
+// SaveExcelInfoAndSheet 编辑保存
+func SaveExcelInfoAndSheet(excelInfo *ExcelInfo, updateExcelInfoParam []string, sheetParamsList []AddExcelSheetParams) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+		} else {
+			_ = o.Commit()
+		}
+	}()
+
+	// 表格信息入库
+	_, err = o.Update(excelInfo, updateExcelInfoParam...)
+	if err != nil {
+		return
+	}
+
+	// 先删除历史的sheet信息
+	sql := `DELETE FROM excel_sheet WHERE excel_info_id = ?`
+	_, err = o.Raw(sql, excelInfo.ExcelInfoId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 再删除历史sheet中的cell data信息
+	sql = `DELETE FROM excel_sheet_data WHERE excel_info_id = ?`
+	_, err = o.Raw(sql, excelInfo.ExcelInfoId).Exec()
+	if err != nil {
+		return
+	}
+
+	// sheet信息入库
+	for _, sheetInfo := range sheetParamsList {
+		dataNum := len(sheetInfo.DataList)
+
+		//sheet信息入库
+		excelSheetInfo := &ExcelSheet{
+			ExcelSheetId: 0,
+			ExcelInfoId:  excelInfo.ExcelInfoId,
+			SheetName:    sheetInfo.SheetName,
+			PageNum:      dataNum,
+			Index:        sheetInfo.Index,
+			Sort:         sheetInfo.Sort,
+			Config:       sheetInfo.Config,
+			CalcChain:    sheetInfo.CalcChain,
+			ModifyTime:   time.Now(),
+			CreateTime:   time.Now(),
+		}
+		sheetId, tmpErr := o.Insert(excelSheetInfo)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		excelSheetInfo.ExcelSheetId = int(sheetId)
+
+		// data信息入库
+		if dataNum > 0 {
+			for k, _ := range sheetInfo.DataList {
+				sheetInfo.DataList[k].ExcelSheetId = excelSheetInfo.ExcelSheetId
+				sheetInfo.DataList[k].ExcelInfoId = excelSheetInfo.ExcelInfoId
+			}
+			_, tmpErr = o.InsertMulti(dataNum, sheetInfo.DataList)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+		}
+	}
+
+	return
+}

+ 98 - 0
models/data_manage/excel/excel_sheet.go

@@ -0,0 +1,98 @@
+package excel
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ExcelSheet excel表格详情表
+type ExcelSheet struct {
+	ExcelSheetId int       `orm:"column(excel_sheet_id);pk"`
+	ExcelInfoId  int       `description:"excel的id"`
+	SheetName    string    `description:"sheet名称"`
+	PageNum      int       `description:"总页码数"`
+	Index        string    `description:"excel数据中的index"`
+	Sort         int       `description:"排序"`
+	Config       string    `description:"配置信息"`
+	CalcChain    string    `description:"计算公式"`
+	ModifyTime   time.Time `description:"最近修改日期"`
+	CreateTime   time.Time `description:"创建日期"`
+}
+
+// Update 更新 excel表格的sheet基础信息
+func (excelSheet *ExcelSheet) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(excelSheet, cols...)
+
+	return
+}
+
+// AddExcelSheet 新增excel表格的sheet基础信息
+func AddExcelSheet(excelInfo *ExcelSheet) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 表格信息入库
+	lastId, err := o.Insert(excelInfo)
+	if err != nil {
+		return
+	}
+	excelInfo.ExcelInfoId = int(lastId)
+
+	return
+}
+
+// GetAllSheetList 根据excel_id获取所有的sheet
+func GetAllSheetList(excelInfoId int) (item []*ExcelSheet, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *
+FROM excel_sheet WHERE 1=1 AND excel_info_id = ? `
+	sql += " ORDER BY sort asc "
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&item)
+	return
+}
+
+// SheetItem excel表格详情表
+type SheetItem struct {
+	ExcelSheetId int             `orm:"column(excel_sheet_id);pk" json:"-"`
+	ExcelInfoId  int             `description:"excel的id"  json:"-"`
+	SheetName    string          `description:"sheet名称"`
+	PageNum      int             `description:"数据总页码数"`
+	Index        string          `description:"excel数据中的index"`
+	Sort         int             `description:"排序"`
+	Config       string          `description:"sheet配置"`
+	CalcChain    string          `description:"计算公式"`
+	ModifyTime   time.Time       `description:"最近修改日期" json:"-"`
+	CreateTime   time.Time       `description:"创建日期"`
+	Data         *ExcelSheetData `description:"excel的数据"`
+}
+
+// GetAllSheetItemList 根据excel_id获取所有的sheet详情
+func GetAllSheetItemList(excelInfoId int) (item []*SheetItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *
+FROM excel_sheet WHERE 1=1 AND excel_info_id = ? `
+	sql += " ORDER BY sort asc "
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&item)
+	return
+}
+
+// GetAllNoConfigSheetItemList 根据excel_id获取所有的sheet详情
+func GetAllNoConfigSheetItemList(excelInfoId int) (item []*SheetItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT excel_sheet_id,excel_info_id,sheet_name,sort,page_num,create_time
+FROM excel_sheet WHERE 1=1 AND excel_info_id = ? `
+	sql += " ORDER BY sort asc "
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&item)
+	return
+}
+
+// AddExcelSheetParams excel表格详情表
+type AddExcelSheetParams struct {
+	ExcelSheetId int               `orm:"column(excel_sheet_id);pk"`
+	ExcelInfoId  int               `description:"excel的id"`
+	SheetName    string            `description:"sheet名称"`
+	Index        string            `description:"excel数据中的index"`
+	Sort         int               `description:"排序"`
+	Config       string            `description:"配置信息"`
+	CalcChain    string            `description:"计算公式"`
+	DataList     []*ExcelSheetData `description:"excel的数据"`
+}

+ 63 - 0
models/data_manage/excel/excel_sheet_data.go

@@ -0,0 +1,63 @@
+package excel
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ExcelSheetData excel表格详情表
+type ExcelSheetData struct {
+	ExcelDataId  int       `orm:"column(excel_data_id);pk"`
+	ExcelInfoId  int       `description:"数据归属的excel_info的id"`
+	ExcelSheetId int       `description:"数据归属sheet"`
+	Sort         int       `description:"数据排序"`
+	Data         string    `description:"数据,分页存储"`
+	ModifyTime   time.Time `description:"最近修改日期"`
+	CreateTime   time.Time `description:"创建日期"`
+}
+
+// Update 更新 excel表格的sheet基础信息
+func (ExcelSheetData *ExcelSheetData) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(ExcelSheetData, cols...)
+
+	return
+}
+
+// AddExcelSheetData 新增excel表格的sheet基础信息
+func AddExcelSheetData(excelInfo *ExcelSheetData) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 表格信息入库
+	lastId, err := o.Insert(excelInfo)
+	if err != nil {
+		return
+	}
+	excelInfo.ExcelInfoId = int(lastId)
+
+	return
+}
+
+// GetSheetDataListBySheetIdListAndPage 根据sheet_id列表和页码获取所有的sheet数据详情
+func GetSheetDataListBySheetIdListAndPage(excelSheetIdList []int, page int) (items []*ExcelSheetData, err error) {
+	num := len(excelSheetIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *
+FROM excel_sheet_data WHERE 1=1 AND excel_sheet_id in (` + utils.GetOrmInReplace(num) + `) AND sort = ? `
+	_, err = o.Raw(sql, excelSheetIdList, page).QueryRows(&items)
+
+	return
+}
+
+// GetAllSheetDataListByExcelInfoId 根据表格id获取所有的sheet的所有数据详情
+func GetAllSheetDataListByExcelInfoId(excelInfoId int) (items []*ExcelSheetData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *
+FROM excel_sheet_data WHERE 1=1 AND excel_info_id = ? ORDER BY sort ASC `
+	_, err = o.Raw(sql, excelInfoId).QueryRows(&items)
+
+	return
+}

+ 45 - 0
models/data_manage/excel/request/excel.go

@@ -0,0 +1,45 @@
+package request
+
+// SaveExcelInfoReq 编辑表格请求
+type SaveExcelInfoReq struct {
+	ExcelInfoId     int         `description:"表格ID"`
+	ExcelName       string      `description:"表格名称"`
+	Source          int         `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
+	ExcelType       int         `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelImage      string      `description:"表格截图"`
+	ExcelClassifyId int         `description:"分类id"`
+	Content         string      `description:"Excel表格内容"`
+	TableData       interface{} `description:"自定义表格的数据内容"`
+	OpSheetList     []SheetOp   `description:"sheet操作"`
+}
+
+type SheetOp struct {
+	SheetIndex int    `description:"对应的sheet下标"`
+	SheetName  string `description:"对应的sheet名称"`
+	OpType     string `description:"操作类型,新增:add;替换:replace,追加:append"`
+}
+
+type AddEdb struct {
+	ExcelInfoId     int      `description:"表格ID"`
+	DateSequenceStr string   `description:"日期序列"`
+	DateSequenceVal []string `description:"日期序列的值列表"`
+	DataSequenceStr string   `description:"数据序列"`
+	DataSequenceVal []string `description:"数据序列的值列表"`
+	EdbName         string   `description:"指标名称"`
+	ClassifyId      int      `description:"分类id"`
+	Frequency       string   `description:"频率"`
+	Unit            string   `description:"单位"`
+}
+
+type EditEdb struct {
+	ExcelInfoId     int      `description:"表格ID"`
+	EdbInfoId       int      `description:"指标ID"`
+	DateSequenceStr string   `description:"日期序列"`
+	DateSequenceVal []string `description:"日期序列的值列表"`
+	DataSequenceStr string   `description:"数据序列"`
+	DataSequenceVal []string `description:"数据序列的值列表"`
+	EdbName         string   `description:"指标名称"`
+	ClassifyId      int      `description:"分类id"`
+	Frequency       string   `description:"频率"`
+	Unit            string   `description:"单位"`
+}

+ 1 - 0
models/data_manage/request/excel_classify.go → models/data_manage/excel/request/excel_classify.go

@@ -5,6 +5,7 @@ type AddExcelClassifyReq struct {
 	ExcelClassifyName string `description:"分类名称"`
 	ParentId          int    `description:"父级id,第一级传0" json:"-"`
 	Level             int    `description:"层级,第一级传0,其余传上一级的层级" json:"-"`
+	Source            int    `description:"1:excel插件的表格,2:自定义表格,3:混合表格,默认:1"`
 }
 
 // EditExcelClassifyReq 修改excel分类请求

+ 17 - 0
models/data_manage/request/excel_info.go → models/data_manage/excel/request/excel_info.go

@@ -15,6 +15,7 @@ type DeleteExcelInfoReq struct {
 
 // AddExcelInfoReq 新增表格请求
 type AddExcelInfoReq struct {
+	ExcelInfoId     int         `description:"表格ID"`
 	ExcelName       string      `description:"表格名称"`
 	Source          int         `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
 	ExcelType       int         `description:"表格类型,1:指标列,2:日期列,默认:1"`
@@ -143,3 +144,19 @@ type MixedTableCellDataReq struct {
 	ShowValue string `description:"展示值"`
 	Value     string `description:"实际值"`
 }
+
+// AddAndEditSandbox 添加/编辑沙盘的请求数据
+type AddAndEditSandbox struct {
+	ExcelInfoId          int    `description:"excel表格ID"`
+	Name               string `description:"沙盘名称"`
+	ChartPermissionId  int    `description:"品种权限id"`
+	Content            string `description:"沙盘内容"`
+	PicUrl             string `description:"沙盘图片地址"`
+	SvgData            string `description:"沙盘svg图片数据"`
+}
+
+// MarkEditExcel 标记编辑表格的请求数据
+type MarkEditExcel struct {
+	ExcelInfoId int `description:"表格id"`
+	Status    int `description:"标记状态,1:编辑中,2:编辑完成"`
+}

+ 4 - 2
models/data_manage/response/excel_classify.go → models/data_manage/excel/response/excel_classify.go

@@ -1,9 +1,11 @@
 package response
 
-import "eta/eta_api/models/data_manage"
+import (
+	"eta/eta_api/models/data_manage/excel"
+)
 
 type ExcelClassifyListResp struct {
-	AllNodes []*data_manage.ExcelClassifyItems
+	AllNodes []*excel.ExcelClassifyItems
 }
 
 type ExcelClassifyDeleteCheckResp struct {

+ 14 - 10
models/data_manage/response/excel_info.go → models/data_manage/excel/response/excel_info.go

@@ -1,8 +1,8 @@
 package response
 
 import (
-	"eta/eta_api/models/data_manage"
-	"eta/eta_api/models/data_manage/request"
+	excel2 "eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/services/excel"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
@@ -17,7 +17,7 @@ type AddExcelInfoResp struct {
 // ExcelListResp 表格列表返回数据
 type ExcelListResp struct {
 	Paging *paging.PagingItem
-	List   []*data_manage.MyExcelInfoList
+	List   []*excel2.MyExcelInfoList
 }
 
 // ExcelTableDetailResp  excel表格详情
@@ -43,8 +43,8 @@ type TableDataItem struct {
 
 // TableDetailResp  excel表格详情
 type TableDetailResp struct {
-	ExcelInfo data_manage.ExcelInfo `description:"表格基础信息"`
-	TableData request.TableDataReq  `description:"表格内容"`
+	ExcelInfo excel2.ExcelInfo     `description:"表格基础信息"`
+	TableData request.TableDataReq `description:"表格内容"`
 }
 
 // ExcelInfoDetail excel表格详情(前端使用)
@@ -66,13 +66,17 @@ type ExcelInfoDetail struct {
 	CreateTime      time.Time             `description:"创建日期"`
 	TableData       interface{}           `description:"表格内容"`
 	Button          ExcelInfoDetailButton `description:"操作权限"`
+	CanEdit         bool                  `description:"是否可编辑"`
+	Editor          string                `description:"编辑人"`
 }
 
 // ExcelInfoDetailButton 操作按钮
 type ExcelInfoDetailButton struct {
-	RefreshButton  bool `description:"是否可编辑"`
-	CopyButton     bool `description:"是否可另存为"`
-	DownloadButton bool `description:"是否可下载"`
-	OpButton       bool `description:"是否可编辑"`
-	DeleteButton   bool `description:"是否可删除"`
+	RefreshButton    bool `description:"是否可刷新"`
+	CopyButton       bool `description:"是否可另存为"`
+	DownloadButton   bool `description:"是否可下载"`
+	OpButton         bool `description:"是否可编辑"`
+	DeleteButton     bool `description:"是否可删除"`
+	OpEdbButton      bool `description:"是否可生成指标"`
+	RefreshEdbButton bool `description:"是否可刷新指标"`
 }

+ 33 - 0
models/data_manage/excel/response/sheet.go

@@ -0,0 +1,33 @@
+package response
+
+import (
+	"eta/eta_api/models/data_manage/excel"
+	"time"
+)
+
+// FindExcelInfoResp 根据名称获取excel的信息
+type FindExcelInfoResp struct {
+	IsFind    bool               `description:"是否存在同名文件"`
+	ExcelInfo FindExcelInfo      `description:"表格详情"`
+	SheetList []*excel.SheetItem `description:"sheet列表"`
+}
+
+// FindExcelInfo excel的数据详情
+type FindExcelInfo 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:"创建日期"`
+	Button          ExcelInfoDetailButton `description:"操作权限"`
+	CanEdit         bool                  `description:"是否可编辑"`
+	Editor          string                `description:"编辑人"`
+}

+ 147 - 0
models/data_manage/jiayue_index.go

@@ -0,0 +1,147 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+// DictIndex 嘉悦指标表
+type DictIndex struct {
+	Id                int       `description:"主键"`
+	Code              string    `description:"指标编码"`
+	Name              string    `description:"指标名称"`
+	Unit              string    `description:"单位"`
+	Frequency         string    `description:"更新频率"`
+	Description       string    `description:"描述"`
+	TableName         string    `description:"指标数据存储库表"`
+	SourceType        string    `description:"指标来源,如:wind:万德;manual:手工导入;formula:公式;webisite:网页爬取"`
+	SourceCode        string    `description:"来源编码"`
+	SourceDescription string    `description:"来源说明"`
+	Industry          string    `description:"品种板块"`
+	Type              string    `description:"指标类型"`
+	Commodity         string    `description:"商品名称"`
+	SjbId             int       `description:"SJB_ID"`
+	UserId            int       `description:"所属用户"`
+	RowsCount         int       `description:"指标数据数量"`
+	DateFirst         time.Time `description:"指标开始时间"`
+	DateLast          time.Time `description:"指标最新时间"`
+	TimeLastUpdate    time.Time `description:"最新更新时间"`
+	TimeLastRequest   time.Time `description:"下次更新时间"`
+	Priority          int       `description:"更新优先级"`
+	Status            int       `description:"指标状态"`
+	ShortName         string    `description:"指标简称"`
+	UpdateDescription string    `description:"更新说明"`
+	ForecastFlag      int       `description:"预测标识"`
+	ManualFlag        int       `description:"手动标识"`
+	VariableFlag      int       `description:"有效标识"`
+	MarketDataFlag    int       `description:"市场价标识"`
+	CreateUser        int       `description:"创建用户"`
+	CreateTime        time.Time `description:"创建时间"`
+	UpdateUser        int       `description:"更新用户"`
+	UpdateTime        time.Time `description:"更新时间"`
+}
+
+// BridgeJiaYueIndexParams 桥接服务-获取嘉悦指标入参
+type BridgeJiaYueIndexParams struct {
+	IndexCode         string `json:"index_code" form:"index_code" description:"指标编码"`
+	SourceExtend      string `json:"source_extend" form:"source_extend" description:"来源"`
+	IndexCodeRequired int    `json:"index_code_required" form:"index_code_required" description:"指标编码是否必填: 0-否; 1-是"`
+}
+
+// BridgeJiaYueIndexDataParams 桥接服务-获取嘉悦指标数据入参
+type BridgeJiaYueIndexDataParams struct {
+	BridgeJiaYueIndexParams
+	StartDate string `json:"start_date" form:"start_date" description:"开始日期"`
+	EndDate   string `json:"end_date" form:"end_date" description:"结束日期"`
+}
+
+// BridgeJiaYueResultIndexData 桥接服务-获取嘉悦指标数据响应体
+type BridgeJiaYueResultIndexData struct {
+	Code int                      `json:"code" description:"状态码"`
+	Msg  string                   `json:"msg" description:"提示信息"`
+	Data BridgeJiaYueIndexAndData `json:"data" description:"返回数据"`
+}
+
+// BridgeJiaYueIndexAndData 桥接服务-嘉悦指标和数据
+type BridgeJiaYueIndexAndData struct {
+	Id             int                     `description:"指标自增ID" json:"id"`
+	IndexCode      string                  `description:"指标编码" json:"index_code"`
+	IndexName      string                  `description:"指标名称" json:"index_name"`
+	Unit           string                  `description:"单位" json:"unit"`
+	Frequency      string                  `description:"频度" json:"frequency"`
+	LastDate       time.Time               `description:"指标最新时间" json:"last_date"`
+	LastUpdateTime time.Time               `description:"最新更新时间" json:"last_update_time"`
+	Status         int                     `description:"指标状态" json:"status"`
+	IndexData      []BridgeJiaYueIndexData `description:"指标数据" json:"index_data"`
+}
+
+// BridgeJiaYueIndexData 桥接服务-嘉悦指标数据
+type BridgeJiaYueIndexData struct {
+	Val        float64   `json:"val"`
+	DataTime   time.Time `json:"data_time"`
+	UpdateTime time.Time `json:"update_time"`
+}
+
+// BridgeJiaYueResultFrequencyList 桥接服务-频度列表响应体
+type BridgeJiaYueResultFrequencyList struct {
+	Code int      `json:"code" description:"状态码"`
+	Msg  string   `json:"msg" description:"提示信息"`
+	Data []string `json:"data" description:"返回数据"`
+}
+
+// BridgeJiaYueResultIndexPageList 桥接服务-指标分页列表响应体
+type BridgeJiaYueResultIndexPageList struct {
+	Code int                       `json:"code" description:"状态码"`
+	Msg  string                    `json:"msg" description:"提示信息"`
+	Data BridgeJiaYuePageIndexResp `json:"data" description:"返回数据"`
+}
+
+// BridgeJiaYuePageIndexReq 桥接服务-指标分页列表请求体
+type BridgeJiaYuePageIndexReq struct {
+	SourceExtend string `json:"source_extend" form:"source_extend" description:"来源"`
+	Keyword      string `json:"keyword" form:"keyword" description:"关键词"`
+	Frequency    string `json:"frequency" form:"frequency" description:"频度"`
+	PageIndex    int    `json:"page_index" form:"page_index" description:"当前页码"`
+	PageSize     int    `json:"page_size" form:"page_size" description:"每页数据量"`
+	SortField    int    `json:"sort_field" form:"sort_field" description:"排序字段: 1-指标开始时间; 2-指标最新时间; 3-更新时间"`
+	SortRule     int    `json:"sort_rule" form:"sort_rule" description:"排序方式: 1-正序; 2-倒序"`
+}
+
+// BridgeJiaYuePageIndexResp 桥接服务-指标分页列表响应体
+type BridgeJiaYuePageIndexResp struct {
+	Total int         `description:"数据总量"`
+	List  []DictIndex `description:"列表数据"`
+}
+
+// JiaYueIndexPageListResp 指标分页列表响应体
+type JiaYueIndexPageListResp struct {
+	List   []*DictIndexItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// DictIndexItem 指标信息
+type DictIndexItem struct {
+	Id             int    `description:"主键"`
+	Code           string `description:"指标编码"`
+	Name           string `description:"指标名称"`
+	Unit           string `description:"单位"`
+	Frequency      string `description:"频度"`
+	DateFirst      string `description:"指标开始时间"`
+	DateLast       string `description:"指标最新时间"`
+	TimeLastUpdate string `description:"最新更新时间"`
+}
+
+// FormatDictIndex2Item 格式化数据宝指标信息
+func FormatDictIndex2Item(origin DictIndex) (item *DictIndexItem) {
+	item = new(DictIndexItem)
+	item.Id = origin.Id
+	item.Code = origin.SourceCode
+	item.Name = origin.Name
+	item.Unit = origin.Unit
+	item.Frequency = origin.Frequency
+	item.DateFirst = utils.TimeTransferString(utils.FormatDate, origin.DateFirst)
+	item.DateLast = utils.TimeTransferString(utils.FormatDate, origin.DateLast)
+	item.TimeLastUpdate = utils.TimeTransferString(utils.FormatDateTime, origin.TimeLastUpdate)
+	return
+}

+ 48 - 6
models/db.go

@@ -3,12 +3,14 @@ package models
 import (
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/excel"
 	future_good2 "eta/eta_api/models/data_manage/future_good"
 	"eta/eta_api/models/data_manage/supply_analysis"
 	"eta/eta_api/models/eta_trial"
 	"eta/eta_api/models/ppt_english"
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/semantic_analysis"
+	"eta/eta_api/models/smart_report"
 	"eta/eta_api/models/system"
 	"eta/eta_api/models/yb"
 	"eta/eta_api/utils"
@@ -134,12 +136,25 @@ func init() {
 	// 商家配置
 	initBusinessConf()
 
+	// 图库框架
+	initChartFramework()
+
 	// 外部链接
 	initOutLink()
+
+	// 智能研报
+	initSmartReport()
+
 	// ETA试用相关表
 	if utils.BusinessCode == utils.BusinessCodeSandbox {
 		initEtaTrial()
 	}
+
+	// 初始化EXCEL的表
+	initExcel()
+
+	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
+	data_manage.InitEdbSourceVar()
 }
 
 // initSystem 系统表 数据表
@@ -195,6 +210,7 @@ func initReport() {
 		new(ClassifyMenuRelation),          // 报告分类-子目录关联表
 		new(ChartPermissionChapterMapping), // 权限mapping表
 		new(ReportChapterType),             // 报告章节类型表
+		new(ReportStateRecord),             // 研报状态修改记录表
 	)
 }
 
@@ -212,9 +228,10 @@ func initYb() {
 func initSandbox() {
 	//注册对象
 	orm.RegisterModel(
-		new(sandbox.Sandbox),        //沙盘主表
-		new(sandbox.SandboxVersion), //沙盘版本表
-		new(sandbox.SandboxDraft),   //沙盘草稿表
+		new(sandbox.Sandbox),         //沙盘主表
+		new(sandbox.SandboxVersion),  //沙盘版本表
+		new(sandbox.SandboxDraft),    //沙盘草稿表
+		new(sandbox.SandboxClassify), //沙盘分类表
 	)
 }
 
@@ -260,9 +277,6 @@ func initEdbData() {
 		new(data_manage.EdbInfoCalculateMapping),
 		new(data_manage.EdbDataCalculateZjpj),            //直接拼接
 		new(data_manage.EdbDataCalculateLjztbpj),         //累计同比值拼接
-		new(data_manage.ExcelClassify),                   //ETA excel表格分类
-		new(data_manage.ExcelInfo),                       //ETA excel表格
-		new(data_manage.ExcelDraft),                      //ETA excel表格草稿
 		new(data_manage.PredictEdbConf),                  //预测指标配置
 		new(data_manage.BaseFromMysteelChemicalClassify), //预测指标配置
 		new(data_manage.BaseFromMysteelChemicalIndex),    //钢联化工
@@ -414,3 +428,31 @@ func initEtaTrial() {
 		new(eta_trial.QuestionnaireFillRecord), // 问卷填写记录表
 	)
 }
+
+// initChartFramework 图库框架相关表
+func initChartFramework() {
+	orm.RegisterModel(
+		new(data_manage.ChartFramework),     // 图库框架主表
+		new(data_manage.ChartFrameworkNode), // 图库框架节点表
+	)
+}
+
+// initExcel 初始化EXCEL
+func initExcel() {
+	orm.RegisterModel(
+		new(excel.ExcelClassify),   //ETA excel表格分类
+		new(excel.ExcelInfo),       //ETA excel表格
+		new(excel.ExcelDraft),      //ETA excel表格草稿
+		new(excel.ExcelSheet),      //ETA excel sheet
+		new(excel.ExcelSheetData),  //ETA excel sheet data
+		new(excel.ExcelEdbMapping), //ETA excel 与 指标 的关系表
+	)
+}
+
+// initSmartReport 智能研报相关表
+func initSmartReport() {
+	orm.RegisterModel(
+		new(smart_report.SmartReport),        // 智能研报主表
+		new(smart_report.SmartReportSaveLog), // 智能研报-保存记录表
+	)
+}

+ 3 - 3
models/report.go

@@ -55,7 +55,7 @@ type ReportList struct {
 	Frequency          string                    `description:"频度"`
 	CreateTime         string                    `description:"创建时间"`
 	ModifyTime         time.Time                 `description:"修改时间"`
-	State              int                       `description:"1:未发布,2:已发布"`
+	State              int                       `description:"1:未发布,2:已发布 3:已驳回 4:已审批"`
 	PublishTime        string                    `description:"发布时间"`
 	PrePublishTime     string                    `description:"预发布时间"`
 	Stage              int                       `description:"期数"`
@@ -122,7 +122,7 @@ func GetReportList(condition string, pars []interface{}, companyType string, sta
 	if condition != "" {
 		sql += condition
 	}
-	sql += `ORDER BY state ASC, modify_time DESC LIMIT ?,?`
+	sql += `ORDER BY FIELD(state,3,1,2,4), modify_time DESC LIMIT ?,?`
 	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
 	return
 }
@@ -1049,4 +1049,4 @@ func SetPrePublishReportById(reportId int, prePublishTime string, preMsgSend int
 	sql := `UPDATE report SET pre_publish_time=?, pre_msg_send=? WHERE id = ? and state = 1 `
 	_, err = o.Raw(sql, prePublishTime, preMsgSend, reportId).Exec()
 	return
-}
+}

+ 22 - 0
models/report_state_record.go

@@ -0,0 +1,22 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type ReportStateRecord struct {
+	Id         int       `orm:"column(id)" description:"Id"`
+	ReportId   int       // 研报id
+	ReportType int       // 报告类型'报告类型:1中文研报2智能研报'
+	State      int       // 状态:1-未提交 2-待审核 3-驳回 4-审核
+	AdminId    int       // 操作人id
+	AdminName  string    // 操作人姓名
+	CreateTime time.Time // 创建时间
+}
+
+func AddReportStateRecord(item *ReportStateRecord) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err = o.Insert(item)
+	return
+}

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

@@ -26,3 +26,14 @@ type DeleteSandbox struct {
 type DeleteSandboxVersion struct {
 	SandboxVersionCode string `description:"沙盘版本code"`
 }
+
+// AddAndEditSandboxV2 添加/编辑沙盘的请求数据
+type AddAndEditSandboxV2 struct {
+	SandboxId         int    `description:"沙盘id"`
+	Name              string `description:"沙盘名称"`
+	Content           string `description:"沙盘内容"`
+	MindmapData       string `description:"思维导图内容"`
+	PicUrl            string `description:"沙盘图片地址"`
+	SvgData           string `description:"沙盘svg图片数据"`
+	SandboxClassifyId int    `description:"分类id"`
+}

+ 1 - 1
models/sandbox/response/sandbox.go

@@ -8,7 +8,7 @@ import (
 // SandboxListResp 沙盘列表返回数据
 type SandboxListResp struct {
 	Paging *paging.PagingItem
-	List   []*sandbox.SandboxListItem
+	List   []*sandbox.Sandbox
 }
 
 // SandboxVersionListResp 沙盘版本列表返回数据

+ 223 - 15
models/sandbox/sandbox.go

@@ -1,25 +1,47 @@
 package sandbox
 
 import (
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
 )
 
 // Sandbox 沙盘推演主表
+//type Sandbox struct {
+//	SandboxId           int       `orm:"column(sandbox_id);pk" description:"沙盘id"`
+//	Name                string    `description:"沙盘名称"`
+//	ChartPermissionId   int       `description:"品种id"`
+//	ChartPermissionName string    `description:"品种名称"`
+//	CurrVersion         int       `description:"当前版本"`
+//	Code                string    `description:"沙盘code"`
+//	Content             string    `description:"沙盘数据"`
+//	PicUrl              string    `description:"沙盘图片地址"`
+//	OpUserId            int       `description:"最近一次编辑操作的用户id"`
+//	OpUserName          string    `description:"最近一次编辑的用户名称(冗余字段,避免查表)"`
+//	IsDelete            int8      `description:"是否删除,0:未删除,1:已删除"`
+//	ModifyTime          time.Time `description:"修改时间"`
+//	CreateTime          time.Time `description:"创建时间"`
+//	SandboxClassifyId   int       `description:"分类id"`
+//	Sort                int       `description:"排序"`
+//}
+
 type Sandbox struct {
-	SandboxId           int       `orm:"column(sandbox_id);pk" description:"沙盘id"`
-	Name                string    `description:"沙盘名称"`
-	ChartPermissionId   int       `description:"品种id"`
-	ChartPermissionName string    `description:"品种名称"`
-	CurrVersion         int       `description:"当前版本"`
-	Code                string    `description:"沙盘code"`
-	Content             string    `description:"沙盘数据"`
-	PicUrl              string    `description:"沙盘图片地址"`
-	OpUserId            int       `description:"最近一次编辑操作的用户id"`
-	OpUserName          string    `description:"最近一次编辑的用户名称(冗余字段,避免查表)"`
-	IsDelete            int8      `description:"是否删除,0:未删除,1:已删除"`
-	ModifyTime          time.Time `description:"修改时间"`
-	CreateTime          time.Time `description:"创建时间"`
+	SandboxId         int       `orm:"column(sandbox_id);pk" description:"沙盘id"`
+	Name              string    `description:"沙盘名称"`
+	Code              string    `description:"沙盘code"`
+	Content           string    `description:"沙盘数据"`
+	MindmapData       string    `description:"思维导图数据"`
+	PicUrl            string    `description:"沙盘图片地址"`
+	SysUserId         int       `description:"作者id"`
+	SysUserName       string    `description:"作者名称"`
+	IsDelete          int8      `description:"是否删除,0:未删除,1:已删除"`
+	ModifyTime        time.Time `description:"修改时间"`
+	CreateTime        time.Time `description:"创建时间"`
+	SandboxClassifyId int       `description:"分类id"`
+	Sort              int       `description:"排序"`
 }
 
 // Update 沙盘字段变更
@@ -172,9 +194,9 @@ type SandboxListItem struct {
 }
 
 // GetList 获取沙盘列表页
-func GetList(condition string, pars []interface{}, startSize, pageSize int) (total int, list []*SandboxListItem, err error) {
+func GetList(condition string, pars []interface{}, startSize, pageSize int) (total int, list []*Sandbox, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := "select a.sandbox_id,a.name,a.chart_permission_id,a.chart_permission_name,a.curr_version,a.code,a.pic_url,a.op_user_id,a.op_user_name,a.modify_time,a.create_time,b.version_code from sandbox as a join sandbox_version b on a.sandbox_id=b.sandbox_id and a.curr_version=b.curr_version where 1=1 AND a.is_delete = 0 "
+	sql := "select a.sandbox_id,a.name,a.code,a.pic_url,a.sys_user_id,a.sys_user_name,a.modify_time,a.create_time from sandbox as a where 1=1 AND a.is_delete = 0 "
 	sql += condition
 	sql += ` order by a.modify_time desc,a.sandbox_id desc`
 
@@ -193,3 +215,189 @@ type SandboxSaveResp struct {
 	*Sandbox
 	VersionCode string `description:"版本号"`
 }
+
+func GetSandboxAll() (list []*SandboxClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT sandbox_id,sandbox_classify_id,name AS sandbox_classify_name, sort
+		FROM sandbox `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// CheckOpSandboxPermission 判断沙盘操作权限
+func CheckOpSandboxPermission(sysUser *system.Admin, createUserId int) (ok bool) {
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN || sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_FICC_ADMIN {
+		ok = true
+	}
+	// 如果图表创建人与当前操作人相同的话,那么就是允许操作
+	if ok == false && createUserId == sysUser.AdminId {
+		ok = true
+	}
+	// 如果图表权限id 是 1 ,那么允许编辑
+	if ok == false && sysUser.ChartPermission == 1 {
+		ok = true
+	}
+	return
+}
+
+// GetSandboxInfoByAdminId 获取所有我创建的沙盘,用于分类展示
+func GetSandboxInfoByAdminId(adminId int) (items []*SandboxClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT sandbox_id,sandbox_classify_id,name AS sandbox_classify_name,code,
+             sys_user_id,sys_user_name
+            FROM sandbox where sys_user_id = ? ORDER BY sort asc,create_time ASC `
+	_, err = o.Raw(sql, adminId).QueryRows(&items)
+	return
+}
+
+func GetSandboxClassify(sandboxClassifyId int) (sandbox_classify_id string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT GROUP_CONCAT(t.sandbox_classify_id) AS sandbox_classify_id FROM (
+			SELECT a.sandbox_classify_id FROM sandbox_classify AS a 
+			WHERE a.sandbox_classify_id=?
+			UNION ALL
+			SELECT a.sandbox_classify_id FROM sandbox_classify AS a 
+			WHERE a.parent_id=? UNION ALL
+	SELECT
+		sandbox_classify_id 
+	FROM
+		sandbox_classify 
+WHERE
+	parent_id IN ( SELECT sandbox_classify_id FROM sandbox_classify WHERE parent_id = ? )
+			)AS t`
+	err = o.Raw(sql, sandboxClassifyId, sandboxClassifyId, sandboxClassifyId).QueryRow(&sandbox_classify_id)
+	return
+}
+
+type SandboxListItems struct {
+	Sandbox
+	ParentIds string
+}
+
+func GetSandboxListByCondition(condition string, pars []interface{}, startSize, pageSize int) (item []*SandboxListItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM sandbox WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " ORDER BY create_time DESC LIMIT ?,? "
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&item)
+	return
+}
+
+func GetSandboxListCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM sandbox WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+type SandboxListResp struct {
+	Paging *paging.PagingItem
+	List   []*SandboxListItems
+}
+
+func AddSandbox(item *Sandbox) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	lastId, err = o.Insert(item)
+	return
+}
+
+func GetSandboxItemsByClassifyId(sandboxClassifyId int) (list []*SandboxClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT sandbox_id,sandbox_classify_id,name AS sandbox_classify_name, sort
+		FROM sandbox  WHERE sandbox_classify_id = ? AND is_delete = 0  ORDER BY sort `
+	_, err = o.Raw(sql, sandboxClassifyId).QueryRows(&list)
+	return
+}
+
+func GetSandboxAllParentByClassifyId(sandboxClassifyId int) (ids string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT
+	GROUP_CONCAT(DISTINCT m.sandbox_classify_id  ORDER BY m.level) AS ids 
+FROM
+	(
+	SELECT
+		@id AS _id,(
+		SELECT
+			@id := parent_id 
+		FROM
+			sandbox_classify 
+		WHERE
+			sandbox_classify_id = _id 
+		) 
+	FROM
+		(
+		SELECT
+			@id :=(
+			SELECT
+				parent_id 
+			FROM
+				sandbox_classify 
+			WHERE
+				sandbox_classify_id = ? 
+			)) vm,
+		sandbox_classify m 
+	WHERE
+		@id IS NOT NULL 
+	) vm
+	INNER JOIN sandbox_classify m 
+WHERE
+	sandbox_classify_id = vm._id `
+	err = o.Raw(sql, sandboxClassifyId).QueryRow(&ids)
+	return
+}
+
+type MoveSandboxReq struct {
+	SandboxId         int `description:"沙盘ID"`
+	PrevSandboxId     int `description:"上一个沙盘ID"`
+	NextSandboxId     int `description:"下一个沙盘ID"`
+	SandboxClassifyId int `description:"分类id"`
+}
+
+func GetSandboxClassifyCountById(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT count(1) AS count FROM sandbox_classify WHERE sandbox_classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// GetSandboxByClassifyIdAndName 根据分类id和沙盘名获取图表信息
+func GetSandboxByClassifyIdAndName(classifyId int, name string) (item *Sandbox, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM sandbox WHERE sandbox_classify_id = ? and name=? `
+	err = o.Raw(sql, classifyId, name).QueryRow(&item)
+	return
+}
+
+func MoveSandbox(sandboxId, classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` UPDATE  sandbox
+			SET
+			  sandbox_classify_id = ?
+			WHERE sandbox_id = ?`
+	_, err = o.Raw(sql, classifyId, sandboxId).Exec()
+	return
+}
+
+// UpdateSandboxSortByClassifyId 根据沙盘id更新排序
+func UpdateSandboxSortByClassifyId(classifyId, nowSort, prevSandboxId int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update sandbox set sort = ` + updateSort + ` WHERE sandbox_classify_id=?  AND `
+	if prevSandboxId > 0 {
+		sql += ` (sort > ? or (sandbox_id > ` + fmt.Sprint(prevSandboxId) + ` and sort = ` + fmt.Sprint(nowSort) + `))`
+	}
+	_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// GetFirstSandboxByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstSandboxByClassifyId(classifyId int) (item *Sandbox, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM sandbox WHERE sandbox_classify_id=? order by sort asc,sandbox_id asc limit 1`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}

+ 163 - 9
models/sandbox/sandbox_classify.go

@@ -1,6 +1,7 @@
 package sandbox
 
 import (
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -8,14 +9,16 @@ import (
 type SandboxClassify struct {
 	SandboxClassifyId   int       `orm:"column(sandbox_classify_id);pk"`
 	SandboxClassifyName string    `description:"分类名称"`
-	ParentId          int       `description:"父级id"`
-	HasData           int       `description:"是否含有指标数据"`
-	CreateTime        time.Time `description:"创建时间"`
-	ModifyTime        time.Time `description:"修改时间"`
-	SysUserId         int       `description:"创建人id"`
-	SysUserRealName   string    `description:"创建人姓名"`
-	Level             int       `description:"层级"`
-	Sort              int       `description:"排序字段,越小越靠前,默认值:10"`
+	ParentId            int       `description:"父级id"`
+	HasData             int       `description:"是否含有指标数据"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+	SysUserId           int       `description:"创建人id"`
+	SysUserRealName     string    `description:"创建人姓名"`
+	Level               int       `description:"层级"`
+	ChartPermissionId   int       `description:"品种id"`
+	ChartPermissionName string    `description:"品种名称"`
+	Sort                int       `description:"排序字段,越小越靠前,默认值:10"`
 }
 
 func AddSandboxClassify(item *SandboxClassify) (lastId int64, err error) {
@@ -48,7 +51,7 @@ type SandboxClassifyItems struct {
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
 	SysUserId           int       `description:"创建人id"`
-	SysUserRealName     string    `description:"创建人姓名"`
+	SysUserName         string    `description:"创建人姓名"`
 	Level               int       `description:"层级"`
 	Sort                int       `description:"排序字段,越小越靠前,默认值:10"`
 	SandboxId           int       `description:"沙盘id"`
@@ -63,6 +66,8 @@ type AddSandboxClassifyReq struct {
 	SandboxClassifyName string `description:"分类名称"`
 	ParentId            int    `description:"父级id,第一级传0"`
 	Level               int    `description:"层级,第一级传0,其余传上一级的层级"`
+	ChartPermissionId   int    `description:"品种id"`
+	ChartPermissionName string `description:"品种名称"`
 }
 
 func GetSandboxClassifyCount(sandboxClassifyName string, parentId int) (count int, err error) {
@@ -90,4 +95,153 @@ func GetSandboxClassifyById(classifyId int) (item *SandboxClassify, err error) {
 	sql := `SELECT * FROM sandbox_classify WHERE sandbox_classify_id=? `
 	err = o.Raw(sql, classifyId).QueryRow(&item)
 	return
+}
+
+func EditSandboxClassify(classifyId int, sandboxClassifyName string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE sandbox_classify SET sandbox_classify_name=?,modify_time=NOW() WHERE sandbox_classify_id=? `
+	_, err = o.Raw(sql, sandboxClassifyName, classifyId).Exec()
+	return
+}
+
+type SandboxClassifyDeleteCheckReq struct {
+	SandboxClassifyId int `description:"分类id"`
+}
+
+func GetSandboxInfoCountByClassifyId(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM sandbox AS a
+				WHERE a.sandbox_classify_id IN(
+				SELECT t.sandbox_classify_id FROM 
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM sandbox_classify WHERE parent_id IS NOT NULL) rd,
+					 (SELECT @pid := ?) pd 
+				WHERE FIND_IN_SET(parent_id, @pid) > 0 
+				  AND @pid := CONCAT(@pid, ',', sandbox_classify_id) 
+				UNION SELECT * FROM sandbox_classify WHERE sandbox_classify_id = @pid
+				)AS t
+				) `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+type SandboxClassifyDeleteCheckResp struct {
+	DeleteStatus int    `description:"检测状态:0:默认值,如果为0,继续走其他校验,1:该分类下关联图表不可删除,2:确认删除当前目录及包含的子目录吗"`
+	TipsMsg      string `description:"提示信息"`
+}
+
+type DeleteSandboxClassifyReq struct {
+	SandboxClassifyId int `description:"分类id"`
+	SandboxId         int `description:"指标id"`
+}
+
+func DeleteSandboxClassify(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` DELETE FROM sandbox_classify
+				WHERE sandbox_classify_id IN(
+				SELECT t.sandbox_classify_id FROM
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM sandbox_classify WHERE parent_id IS NOT NULL) rd,
+				(SELECT @pid := ?) pd
+				WHERE FIND_IN_SET(parent_id, @pid) > 0
+				AND @pid := CONCAT(@pid, ',', sandbox_classify_id)
+				UNION SELECT * FROM sandbox_classify WHERE sandbox_classify_id = @pid
+				)AS t
+				) `
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}
+
+// MoveSandboxClassifyReq 移动沙盘分类请求参数
+type MoveSandboxClassifyReq struct {
+	ClassifyId       int `description:"分类id"`
+	SandboxId        int `description:"沙盘ID"`
+	ParentClassifyId int `description:"父级分类id 移动沙盘时为目标分类id"`
+	PrevId           int `description:"上一个兄弟节点分类id"`
+	NextId           int `description:"下一个兄弟节点分类id"`
+	PrevType         int `description:"上一个兄弟节点类型 1分类 2沙盘 "`
+	NextType         int `description:"上一个兄弟节点类型 1分类 2沙盘 "`
+}
+
+// UpdateSandboxClassifySortByParentId 根据沙盘父类id更新排序
+func UpdateSandboxClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update sandbox_classify set sort = ` + updateSort + ` WHERE parent_id=? and sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( sandbox_classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstSandboxClassifyByParentId 获取当前父级沙盘分类下的排序第一条的数据
+func GetFirstSandboxClassifyByParentId(parentId int) (item *SandboxClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM sandbox_classify WHERE parent_id=? order by sort asc,sandbox_classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// Update 更新沙盘分类基础信息
+func (sandboxClassify *SandboxClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(sandboxClassify, cols...)
+	return
+}
+
+// GetSandboxClassifyAndInfoByParentId
+func GetSandboxClassifyAndInfoByParentId(parentId int) (items []*SandboxClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT
+	0 AS sandbox_id,
+	sandbox_classify_id,
+	sandbox_classify_name,
+	parent_id,
+	create_time,
+	modify_time,
+	sys_user_id,
+	sys_user_real_name AS sys_user_name,
+	sort,
+	level,
+	0 AS is_delete
+FROM
+	sandbox_classify 
+WHERE
+	parent_id = ? UNION ALL
+SELECT
+	sandbox_id,
+	sandbox_classify_id,
+	name AS sandbox_classify_name,
+	0 AS parent_id,
+	create_time,
+	modify_time,
+	sys_user_id,
+	sys_user_name,
+	sort,
+	0 AS level,
+	is_delete 
+FROM
+	sandbox 
+WHERE
+	sandbox_classify_id = ? AND is_delete = 0
+ORDER BY
+	sort ASC,
+	sandbox_classify_id ASC`
+	_, err = o.Raw(sql, parentId, parentId).QueryRows(&items)
+	return
+}
+
+type SandboxLinkCheckReq struct {
+	EdbInfoIdList   []int `description:"指标id列表"`
+	ChartInfoIdList []int `description:"图库id列表"`
+	ReportIdList    []int `description:"报告id列表"`
+}
+
+
+type SandboxLinkCheckResp struct {
+	EdbInfoIdList   []int `description:"指标id列表"`
+	ChartInfoIdList []int `description:"图库id列表"`
+	ReportIdList    []int `description:"报告id列表"`
 }

+ 345 - 0
models/smart_report/smart_report.go

@@ -0,0 +1,345 @@
+package smart_report
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"strings"
+	"time"
+)
+
+const (
+	SmartReportStateWaitPublish = 1
+	SmartReportStatePublished   = 2
+)
+
+// SmartReport 智能研报
+type SmartReport struct {
+	SmartReportId       int       `orm:"column(smart_report_id);pk" description:"智能研报ID"`
+	ReportCode          string    `description:"报告唯一编码"`
+	ClassifyIdFirst     int       `description:"一级分类ID"`
+	ClassifyNameFirst   string    `description:"一级分类名称"`
+	ClassifyIdSecond    int       `description:"二级分类ID"`
+	ClassifyNameSecond  string    `description:"二级分类名称"`
+	AddType             int       `description:"新增方式:1-新增报告;2-继承报告"`
+	Title               string    `description:"标题"`
+	Abstract            string    `description:"摘要"`
+	Author              string    `description:"作者"`
+	Frequency           string    `description:"频度"`
+	Stage               int       `description:"期数"`
+	Content             string    `description:"内容"`
+	ContentSub          string    `description:"内容前两个章节"`
+	ContentStruct       string    `description:"内容组件"`
+	VideoUrl            string    `description:"音频文件URL"`
+	VideoName           string    `description:"音频文件名称"`
+	VideoPlaySeconds    float64   `description:"音频播放时长"`
+	VideoSize           string    `description:"音频文件大小,单位M"`
+	AdminId             int       `description:"创建者ID"`
+	AdminRealName       string    `description:"创建者姓名"`
+	State               int       `description:"发布状态:1-待发布;2-已发布"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	Pv                  int       `description:"pv"`
+	Uv                  int       `description:"uv"`
+	PublishTime         time.Time `description:"发布时间"`
+	PrePublishTime      time.Time `description:"预发布时间"`
+	PreMsgSend          int       `description:"定时发布后是否推送模版消息:0-否;1-是"`
+	MsgIsSend           int       `description:"消息是否已发送:0-否;1-是"`
+	MsgSendTime         time.Time `description:"模版消息发送时间"`
+	DetailImgUrl        string    `description:"报告详情长图地址"`
+	DetailPdfUrl        string    `description:"报告详情PDF地址"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+}
+
+func (m *SmartReport) TableName() string {
+	return "smart_report"
+}
+
+func (m *SmartReport) PrimaryId() string {
+	return "smart_report_id"
+}
+
+func (m *SmartReport) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.SmartReportId = int(id)
+	return
+}
+
+func (m *SmartReport) CreateMulti(items []*SmartReport) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SmartReport) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SmartReport) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.SmartReportId).Exec()
+	return
+}
+
+func (m *SmartReport) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SmartReport) GetItemById(id int) (item *SmartReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SmartReport) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *SmartReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *SmartReport) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SmartReport) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SmartReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SmartReport) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SmartReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY FIELD(state,3,1,2,4), create_time DESC`
+	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
+}
+
+func (m *SmartReport) GetMaxStageByClassifyId(classifyId int) (stage int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT MAX(stage) AS max_stage FROM %s WHERE classify_id_second = ?`, m.TableName())
+	err = o.Raw(sql, classifyId).QueryRow(&stage)
+	return
+}
+
+// SmartReportItem 智能研报信息
+type SmartReportItem struct {
+	SmartReportId       int     `description:"智能研报ID"`
+	ReportCode          string  `description:"报告唯一编码"`
+	ClassifyIdFirst     int     `description:"一级分类ID"`
+	ClassifyNameFirst   string  `description:"一级分类名称"`
+	ClassifyIdSecond    int     `description:"二级分类ID"`
+	ClassifyNameSecond  string  `description:"二级分类名称"`
+	AddType             int     `description:"新增方式:1-新增报告;2-继承报告"`
+	Title               string  `description:"标题"`
+	Abstract            string  `description:"摘要"`
+	Author              string  `description:"作者"`
+	Frequency           string  `description:"频度"`
+	Stage               int     `description:"期数"`
+	Content             string  `description:"内容"`
+	ContentSub          string  `description:"内容前两个章节"`
+	ContentStruct       string  `description:"内容组件"`
+	VideoUrl            string  `description:"音频文件URL"`
+	VideoName           string  `description:"音频文件名称"`
+	VideoPlaySeconds    float64 `description:"音频播放时长"`
+	VideoSize           string  `description:"音频文件大小,单位M"`
+	AdminId             int     `description:"创建者姓名"`
+	AdminRealName       string  `description:"创建者姓名"`
+	LastModifyAdminId   int     `description:"最后更新人ID"`
+	LastModifyAdminName string  `description:"最后更新人姓名"`
+	ContentModifyTime   string  `description:"内容更新时间"`
+	Pv                  int     `description:"pv"`
+	Uv                  int     `description:"uv"`
+	State               int     `description:"发布状态:1-待发布;2-已发布"`
+	PublishTime         string  `description:"发布时间"`
+	PrePublishTime      string  `description:"预发布时间"`
+	MsgIsSend           int     `description:"消息是否已发送:0-否;1-是"`
+	MsgSendTime         string  `description:"模版消息发送时间"`
+	DetailImgUrl        string  `description:"报告详情长图地址"`
+	DetailPdfUrl        string  `description:"报告详情PDF地址"`
+	CreateTime          string  `description:"创建时间"`
+	ModifyTime          string  `description:"修改时间"`
+	CanEdit             bool    `description:"是否可编辑"`
+	Editor              string  `description:"当前编辑人"`
+}
+
+// FormatSmartReport2Item 格式化智能研报数据格式
+func FormatSmartReport2Item(origin *SmartReport) (item *SmartReportItem) {
+	item = new(SmartReportItem)
+	if origin == nil {
+		return
+	}
+	item.SmartReportId = origin.SmartReportId
+	item.ReportCode = origin.ReportCode
+	item.ClassifyIdFirst = origin.ClassifyIdFirst
+	item.ClassifyNameFirst = origin.ClassifyNameFirst
+	item.ClassifyIdSecond = origin.ClassifyIdSecond
+	item.ClassifyNameSecond = origin.ClassifyNameSecond
+	item.AddType = origin.AddType
+	item.Title = origin.Title
+	item.Abstract = origin.Abstract
+	item.Author = origin.Author
+	item.Frequency = origin.Frequency
+	item.Stage = origin.Stage
+	item.Content = html.UnescapeString(origin.Content)
+	item.ContentSub = html.UnescapeString(origin.ContentSub)
+	item.ContentStruct = html.UnescapeString(origin.ContentStruct)
+	item.VideoUrl = origin.VideoUrl
+	item.VideoName = origin.VideoName
+	item.VideoPlaySeconds = origin.VideoPlaySeconds
+	item.VideoSize = origin.VideoSize
+	item.AdminId = origin.AdminId
+	item.AdminRealName = origin.AdminRealName
+	item.LastModifyAdminId = origin.LastModifyAdminId
+	item.LastModifyAdminName = origin.LastModifyAdminName
+	item.ContentModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ContentModifyTime)
+	item.Pv = origin.Pv
+	item.Uv = origin.Uv
+	item.State = origin.State
+	item.PublishTime = utils.TimeTransferString(utils.FormatDateTime, origin.PublishTime)
+	item.PrePublishTime = utils.TimeTransferString(utils.FormatDateTime, origin.PrePublishTime)
+	item.MsgIsSend = origin.MsgIsSend
+	item.MsgSendTime = utils.TimeTransferString(utils.FormatDateTime, origin.MsgSendTime)
+	item.DetailImgUrl = origin.DetailImgUrl
+	item.DetailPdfUrl = origin.DetailPdfUrl
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// SmartReportAddReq 新增智能研报请求体
+type SmartReportAddReq struct {
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类ID"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类ID"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+}
+
+// SmartReportEditReq 编辑智能研报请求体
+type SmartReportEditReq struct {
+	SmartReportAddReq
+	SmartReportId int    `description:"智能研报ID"`
+	Content       string `description:"内容"`
+	ContentStruct string `description:"内容结构"`
+}
+
+// SmartReportRemoveReq 删除智能研报请求体
+type SmartReportRemoveReq struct {
+	SmartReportId int `description:"智能研报ID"`
+}
+
+// SmartReportPublishReq 发布智能研报请求体
+type SmartReportPublishReq struct {
+	SmartReportId int `description:"智能研报ID"`
+	PublishState  int `description:"1-取消发布; 2-发布"`
+}
+
+// SmartReportPrePublishReq 预发布智能研报请求体
+type SmartReportPrePublishReq struct {
+	SmartReportId  int    `description:"智能研报ID"`
+	PrePublishTime string `description:"预发布时间"`
+	PreMsgSend     int    `description:"定时发布成功后是否立即推送模版消息:0否,1是"`
+}
+
+// SmartReportSaveContentReq 保存草稿请求体
+type SmartReportSaveContentReq struct {
+	SmartReportId int    `description:"智能研报ID"`
+	Content       string `description:"内容"`
+	ContentStruct string `description:"内容结构"`
+	NoChange      int    `description:"内容是否未改变:1:内容未改变"`
+}
+
+// SmartReportSaveContentResp 保存草稿响应体
+type SmartReportSaveContentResp struct {
+	SmartReportId int `description:"智能研报ID"`
+}
+
+// SmartReportSendMsgReq 消息推送请求体
+type SmartReportSendMsgReq struct {
+	SmartReportId int `description:"智能研报ID"`
+}
+
+// SmartReportMarkEditReq 标记编辑英文研报的请求数据
+type SmartReportMarkEditReq struct {
+	SmartReportId int `description:"智能研报ID"`
+	Status        int `description:"标记状态: 1-编辑中; 2-编辑完成"`
+}
+
+// SmartReportListResp 智能研报
+type SmartReportListResp struct {
+	List   []*SmartReportItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// ElasticSmartReport 智能研报es
+type ElasticSmartReport struct {
+	SmartReportId      int    `description:"智能研报ID"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	BodyContent        string `description:"内容"`
+	PublishTime        string `description:"发布时间"`
+	PublishState       int    `description:"发布状态 1-未发布 2-已发布"`
+	Author             string `description:"作者"`
+	ClassifyIdFirst    int    `description:"一级分类ID"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类ID"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	StageStr           string `description:"报告期数"`
+	Frequency          string `description:"频度"`
+}
+
+// Report2ImgQueueReq 报告详情生成长图队列请求体
+type Report2ImgQueueReq struct {
+	ReportType int    `description:"报告类型: 1-研报; 2-智能研报"`
+	ReportCode string `description:"报告唯一编码"`
+}

+ 122 - 0
models/smart_report/smart_report_save_log.go

@@ -0,0 +1,122 @@
+package smart_report
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// SmartReportSaveLog 智能研报保存记录
+type SmartReportSaveLog struct {
+	Id            int       `orm:"column(id);pk"`
+	SmartReportId int       `description:"智能研报ID"`
+	Content       string    `description:"内容"`
+	ContentSub    string    `description:"内容前两个章节"`
+	ContentStruct string    `description:"内容组件"`
+	AdminId       int       `description:"操作人ID"`
+	AdminName     string    `description:"操作人姓名"`
+	CreateTime    time.Time `description:"创建时间"`
+}
+
+func (m *SmartReportSaveLog) TableName() string {
+	return "smart_report_save_log"
+}
+
+func (m *SmartReportSaveLog) PrimaryId() string {
+	return "id"
+}
+
+func (m *SmartReportSaveLog) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *SmartReportSaveLog) CreateMulti(items []*SmartReportSaveLog) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *SmartReportSaveLog) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *SmartReportSaveLog) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.Id).Exec()
+	return
+}
+
+func (m *SmartReportSaveLog) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *SmartReportSaveLog) GetItemById(id int) (item *SmartReportSaveLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *SmartReportSaveLog) GetItemByCondition(condition string, pars []interface{}) (item *SmartReportSaveLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT 1`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *SmartReportSaveLog) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *SmartReportSaveLog) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SmartReportSaveLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *SmartReportSaveLog) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*SmartReportSaveLog, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}

+ 1 - 1
models/system/sys_menu.go

@@ -114,7 +114,7 @@ func GetMenuByParentIdRoleIds(roleIds string, parentId int) (items []*ChildMenu,
 			INNER JOIN sys_role AS c ON b.role_id=c.role_id
 			WHERE c.role_id in (` + roleIds + `)
 			AND a.parent_id=? 
-            ORDER BY a.sort ASC`
+            ORDER BY a.sort ASC, create_time DESC, menu_id DESC`
 	_, err = orm.NewOrm().Raw(sql, parentId).QueryRows(&items)
 	return
 }

File diff suppressed because it is too large
+ 625 - 229
routers/commentsRouter.go


+ 20 - 2
routers/router.go

@@ -11,6 +11,7 @@ import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/controllers/data_manage"
 	"eta/eta_api/controllers/data_manage/correlation"
+	"eta/eta_api/controllers/data_manage/excel"
 	future_good2 "eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
@@ -20,6 +21,7 @@ import (
 	"eta/eta_api/controllers/roadshow"
 	"eta/eta_api/controllers/sandbox"
 	"eta/eta_api/controllers/semantic_analysis"
+	"eta/eta_api/controllers/smart_report"
 	"eta/eta_api/controllers/trade_analysis"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/filter/cors"
@@ -149,11 +151,12 @@ func init() {
 				&data_manage.ChartInfoController{},
 				&data_manage.ManualController{},
 				&data_manage.BaseFromChangesVisitorsCovidController{},
-				&data_manage.ExcelClassifyController{},
-				&data_manage.ExcelInfoController{},
+				&excel.ExcelClassifyController{},
+				&excel.ExcelInfoController{},
 				&data_manage.PredictEdbClassifyController{},
 				&data_manage.PredictEdbInfoController{},
 				&data_manage.BaseFromNationalStatisticsController{},
+				&data_manage.JiaYueEdbSourceController{},
 			),
 		),
 		web.NSNamespace("/my_chart",
@@ -266,6 +269,11 @@ func init() {
 				&trade_analysis.TradeAnalysisController{},
 			),
 		),
+		web.NSNamespace("/custom_analysis",
+			web.NSInclude(
+				&excel.CustomAnalysisController{},
+			),
+		),
 		web.NSNamespace("/out_link",
 			web.NSInclude(
 				&controllers.OutLinkController{},
@@ -287,6 +295,16 @@ func init() {
 				&controllers.MeetingProbabilitiesController{},
 			),
 		),
+		web.NSNamespace("/chart_framework",
+			web.NSInclude(
+				&data_manage.ChartFrameworkController{},
+			),
+		),
+		web.NSNamespace("/smart_report",
+			web.NSInclude(
+				&smart_report.SmartReportController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 52 - 0
services/data/base_bridge.go

@@ -0,0 +1,52 @@
+package data
+
+import "time"
+
+// EdbBridge 桥接数据源接口(算了别用了难用的一批, 不如直接判断商户号来的方便快捷...)
+type EdbBridge interface {
+	GetIndex(GetIndexFromBridgeReq) (BridgeIndexItem, error)
+	//GetIndexAndData(GetIndexAndDataFromBridgeReq) (BridgeIndexItem, error)
+}
+
+// GetIndexFromBridgeReq 获取指标信息请求体
+type GetIndexFromBridgeReq struct {
+	IndexCode         string `description:"指标编码"`
+	Source            int    `description:"指标来源ID"`
+	SourceExtend      string `description:"指标来源(用作查询条件)"`
+	IndexCodeRequired int    `description:"指标编码是否必填: 0-否; 1-是"`
+}
+
+// GetIndexAndDataFromBridgeReq 获取指标数据请求体
+type GetIndexAndDataFromBridgeReq struct {
+	IndexCode    string `description:"指标编码"`
+	Source       int    `description:"指标来源ID"`
+	SourceExtend string `description:"指标来源(用作查询条件)"`
+	StartDate    string `description:"数据开始日期"`
+	EndDate      string `description:"数据结束日期"`
+}
+
+// BridgeIndexItem 桥接服务指标信息
+type BridgeIndexItem struct {
+	Id        int                   `description:"自增ID"`
+	IndexCode string                `description:"指标编码"`
+	IndexName string                `description:"指标名称"`
+	Unit      string                `description:"单位"`
+	Frequency string                `description:"频度"`
+	Data      []BridgeIndexDataItem `description:"数据值"`
+}
+
+// BridgeIndexDataItem 桥接服务指标数据信息
+type BridgeIndexDataItem struct {
+	Val        float64   `description:"数据值"`
+	DataTime   time.Time `description:"数据日期"`
+	UpdateTime time.Time `description:"更新时间"`
+}
+
+// InitBridgeOB 初始化桥接服务
+func InitBridgeOB(bridgeKey string) EdbBridge {
+	switch bridgeKey {
+	case "bridge_jiayue":
+		return new(EdbBridgeJiaYue) // 嘉悦物产
+	}
+	return nil
+}

+ 24 - 0
services/data/base_edb_lib.go

@@ -15,6 +15,7 @@ import (
 func AddEdbData(source int, edbCode string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
+	param["Source"] = source
 	urlStr := ``
 	switch source {
 	case utils.DATA_SOURCE_THS:
@@ -66,6 +67,10 @@ func AddEdbData(source int, edbCode string) (resp *models.BaseResponse, err erro
 	case utils.DATA_SOURCE_FUBAO:
 		urlStr = "fubao/add"
 	default:
+		edbSource := data_manage.EdbSourceIdMap[source]
+		if edbSource != nil {
+			urlStr = edbSource.EdbAddMethod
+		}
 	}
 	if urlStr == "" {
 		err = fmt.Errorf("未实现该指标的刷新接口,请联系管理员")
@@ -125,6 +130,7 @@ func RefreshEdbData(edbInfoId, source int, edbCode, startDate string) (resp *mod
 	param["EdbCode"] = edbCode
 	param["EdbInfoId"] = edbInfoId
 	param["StartDate"] = startDate
+	param["Source"] = source
 	urlStr := ``
 	switch source {
 	case utils.DATA_SOURCE_THS:
@@ -179,6 +185,11 @@ func RefreshEdbData(edbInfoId, source int, edbCode, startDate string) (resp *mod
 		urlStr = "national_statistics/refresh"
 	case utils.DATA_SOURCE_FUBAO:
 		urlStr = "fubao/refresh"
+	default:
+		edbSource := data_manage.EdbSourceIdMap[source]
+		if edbSource != nil {
+			urlStr = edbSource.EdbRefreshMethod
+		}
 	}
 	if urlStr == "" {
 		err = fmt.Errorf(fmt.Sprint("source:", source, ";未实现该指标的刷新接口,请联系管理员"))
@@ -287,6 +298,16 @@ func SaveAdjustEdbInfo(param string) (resp *models.BaseResponse, err error) {
 	return
 }
 
+// ResetCustomAnalysisData 重置自定义表格的数据
+func ResetCustomAnalysisData(reqStr string) (resp *AddPredictEdbDataResponse, err error) {
+	_, resultByte, err := postAddEdbData(reqStr, "calculate/custom_analysis/reset")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
 // CalculateComputeCorrelationResp 拟合残差计算相关性的值返回
 type CalculateComputeCorrelationResp struct {
 	Ret         int
@@ -367,6 +388,9 @@ func HttpPost(url, postData string, params ...string) ([]byte, error) {
 	req.Header.Set("Content-Type", contentType)
 	req.Header.Set("authorization", utils.MD5(utils.APP_EDB_LIB_NAME_EN+utils.EDB_LIB_Md5_KEY))
 	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
 	defer resp.Body.Close()
 	b, err := ioutil.ReadAll(resp.Body)
 	fmt.Println("HttpPost:" + string(b))

+ 1 - 1
services/data/chart_info_elastic.go

@@ -1,10 +1,10 @@
 package data
 
 import (
-	"fmt"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/services/elastic"
 	"eta/eta_api/utils"
+	"fmt"
 	"strconv"
 	"strings"
 )

+ 9 - 6
services/data/correlation/chart_info.go

@@ -961,12 +961,15 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin) (cha
 		isSendEmail = false
 		return
 	}
-	endDate, e := time.Parse(utils.FormatDate, req.CorrelationChartInfo.EndDate)
-	if e != nil {
-		errMsg = "结束日期格式有误"
-		err = errors.New(errMsg)
-		isSendEmail = false
-		return
+	var endDate time.Time
+	if req.CorrelationChartInfo.EndDate != `` {
+		endDate, e = time.Parse(utils.FormatDate, req.CorrelationChartInfo.EndDate)
+		if e != nil {
+			errMsg = "结束日期格式有误"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
 	}
 	if len(req.CorrelationChartInfo.EdbInfoIdList) != 2 {
 		errMsg = "请选择AB指标"

+ 28 - 4
services/data/edb_classify.go

@@ -4,7 +4,9 @@ import (
 	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
 	"strconv"
@@ -360,7 +362,7 @@ func EditEdbClassify(classifyId int, classifyName string, sysUser *system.Admin)
 	}
 
 	// 修改数据
-	err = data_manage.EditEdbClassify(classifyId, classifyName)
+	err = data_manage.EditEdbClassify(classifyId, classifyName, sysUser.AdminId, sysUser.RealName)
 	if err != nil {
 		errMsg = "保存失败"
 	}
@@ -566,6 +568,20 @@ func Delete(classifyId, edbInfoId int, sysUser *system.Admin, requestBody, reque
 			}
 		}
 
+		// 判断指标是否用作表格引用
+		{
+			calculateCount, tmpErr := excel.GetNoCustomAnalysisExcelEdbMappingCount(edbInfoId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "删除失败"
+				err = errors.New("当前指标已添加到表格,GetNoCustomAnalysisExcelEdbMappingCount Err:" + tmpErr.Error())
+				return
+			}
+			if calculateCount > 0 {
+				errMsg = "当前指标已添加到表格,不可删除"
+				return
+			}
+		}
+
 		//真实删除
 		tmpErr = data_manage.DeleteEdbInfoAndData(edbInfo.EdbInfoId, edbInfo.Source)
 		if tmpErr != nil {
@@ -574,6 +590,14 @@ func Delete(classifyId, edbInfoId int, sysUser *system.Admin, requestBody, reque
 			return
 		}
 
+		// 如果删除的指标是自定义分析的来源,那么还需要删除指标与excel的关系
+		if edbInfo.Source == utils.DATA_SOURCE_CALCULATE_ZDYFX {
+			tmpErr = excel.DeleteCustomAnalysisExcelEdbMappingByEdbInfoId(edbInfo.EdbInfoId)
+			if tmpErr != nil {
+				alarm_msg.SendAlarmMsg(fmt.Sprintf("删除指标时,需要删除与自定义分析的关系失败,指标ID:%d,Err:%s", edbInfo.EdbInfoId, tmpErr.Error()), 3)
+			}
+		}
+
 		// 返回下一个表格的信息
 		{
 			var condition string
@@ -638,7 +662,7 @@ func MoveEdbClassify(req data_manage.MoveEdbClassifyReq, sysUser *system.Admin,
 	classifyId := req.ClassifyId
 	parentClassifyId := req.ParentClassifyId
 	prevClassifyId := req.PrevClassifyId
-	nextClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
 
 	edbInfoId := req.EdbInfoId
 	prevEdbInfoId := req.PrevEdbInfoId
@@ -895,7 +919,7 @@ func moveEdbClassify(parentEdbClassifyInfo, edbClassifyInfo, prevClassify, nextC
 			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
 			if firstClassify != nil && firstClassify.Sort == 0 {
 				updateSortStr := ` sort + 1 `
-				_ = data_manage.UpdateEdbClassifySortByParentId(parentClassifyId, firstClassify.ClassifyId-1, 0, updateSortStr, edbClassifyInfo.ClassifyType)
+				_ = data_manage.UpdateEdbClassifySortByParentId(parentClassifyId, firstClassify.ClassifyId-1, 0, updateSortStr, classifyType)
 				//该分类下的所有指标也需要+1
 				_ = data_manage.UpdateEdbInfoSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
 			} else {
@@ -1026,7 +1050,7 @@ func moveEdbClassify(parentEdbClassifyInfo, edbClassifyInfo, prevClassify, nextC
 			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
 			if firstClassify != nil && firstClassify.Sort == 0 {
 				updateSortStr := ` sort + 1 `
-				_ = data_manage.UpdateEdbClassifySortByParentId(parentClassifyId, firstClassify.ClassifyId-1, 0, updateSortStr, edbClassifyInfo.ClassifyType)
+				_ = data_manage.UpdateEdbClassifySortByParentId(parentClassifyId, firstClassify.ClassifyId-1, 0, updateSortStr, classifyType)
 				//该分类下的所有指标也需要+1
 				_ = data_manage.UpdateEdbInfoSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
 			} else {

+ 232 - 187
services/data/edb_info.go

@@ -29,194 +29,15 @@ func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll bool) (err error, isA
 
 	err, isAsync = EdbInfoRefreshAllFromBaseV3([]int{edbInfoId}, refreshAll, false)
 	return
-	//// 获取关联的基础指标
-	//newBaseEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr, err, errmsg := getRefreshEdbInfoList(edbInfoId)
-	//if err != nil {
-	//	return
-	//}
-	//
-	//var startDate string
-	//for _, bv := range newBaseEdbInfoArr {
-	//	//source := bv.Source
-	//	//edbInfoId := bv.EdbInfoId
-	//	//edbCode := bv.EdbCode
-	//	if bv.StartDate == "0000-00-00" {
-	//		continue
-	//	}
-	//	//开始时间
-	//	startDate = ``
-	//	if refreshAll { //刷新所有数据,用开始时间作为起始日期去刷新
-	//		sTime, err := time.Parse(utils.FormatDate, bv.StartDate)
-	//		if err != nil {
-	//			return err
-	//		}
-	//		startDate = sTime.Format(utils.FormatDate)
-	//	} else {
-	//		sTime, err := time.Parse(utils.FormatDate, bv.EndDate)
-	//		if err != nil {
-	//			return err
-	//		}
-	//		frequency := bv.Frequency
-	//		var limitDay int
-	//		switch frequency {
-	//		case "日度":
-	//			limitDay = utils.DATA_START_REFRESH_LIMIT
-	//		case "周度":
-	//			limitDay = utils.DATA_START_REFRESH_LIMIT * 7
-	//		case "月度":
-	//			limitDay = utils.DATA_START_REFRESH_LIMIT * 30
-	//		case "季度":
-	//			limitDay = utils.DATA_START_REFRESH_LIMIT * 90
-	//		case "年度":
-	//			limitDay = utils.DATA_START_REFRESH_LIMIT * 365
-	//		default:
-	//			limitDay = utils.DATA_START_REFRESH_LIMIT
-	//		}
-	//		startDate = sTime.AddDate(0, 0, -limitDay).Format(utils.FormatDate)
-	//	}
-	//	result, err := RefreshEdbData(bv.EdbInfoId, bv.Source, bv.EdbCode, startDate)
-	//	if err != nil {
-	//		fmt.Println(bv.EdbInfoId, "RefreshBaseEdbData err", time.Now())
-	//		errmsg = "RefreshBaseEdbData Err:" + err.Error()
-	//		return err
-	//	}
-	//	if result.Ret != 200 {
-	//		fmt.Println(bv.EdbInfoId, "RefreshBaseEdbData err;msg:", result.Msg, ";errMsg:", result.ErrMsg)
-	//		errmsg = fmt.Sprint(bv.EdbInfoId, "RefreshBaseEdbData err;msg:", result.Msg, ";errMsg:", result.ErrMsg)
-	//		return fmt.Errorf("刷新失败, err:", errmsg)
-	//	}
-	//
-	//	//maxAndMinItem, err := data_manage.GetEdbInfoMaxAndMinInfo(source, edbCode)
-	//	//if err != nil {
-	//	//	if err.Error() == utils.ErrNoRow() { //找不到数据,那么就进入到下一条数据做处理
-	//	//		continue
-	//	//	}
-	//	//	return err
-	//	//}
-	//	//if maxAndMinItem != nil {
-	//	//	err = data_manage.ModifyEdbInfoMaxAndMinInfo(edbInfoId, maxAndMinItem)
-	//	//	if err != nil {
-	//	//		return err
-	//	//	}
-	//	//}
-	//	fmt.Println("end newBaseEdbInfoArr:", bv, time.Now())
-	//}
-	//
-	////刷新相关普通计算指标
-	//for _, v := range calculateArr {
-	//	edbInfo := newCalculateMap[v]
-	//	if edbInfo == nil {
-	//		return err
-	//	}
-	//	startDate = edbInfo.StartDate
-	//	source := edbInfo.Source
-	//	if startDate == "" || startDate == "0000-00-00" { //如果没有开始日期,说明还没有计算出来数据,那么就往前面推40年吧(也意味着重新计算了)
-	//		startDate = time.Now().AddDate(-40, 0, 0).Format(utils.FormatDate)
-	//	} else {
-	//		if source == utils.DATA_SOURCE_CALCULATE {
-	//			startDate = ``
-	//			if refreshAll { //刷新所有数据,用开始时间作为起始日期去刷新
-	//				startDate = edbInfo.StartDate
-	//			} else {
-	//				sTime, err := time.Parse(utils.FormatDate, edbInfo.EndDate)
-	//				if err != nil {
-	//					return err
-	//				}
-	//				frequency := edbInfo.Frequency
-	//				var limitDay int
-	//				switch frequency {
-	//				case "日度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT
-	//				case "周度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 7
-	//				case "月度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 30
-	//				case "季度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 90
-	//				case "年度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 365
-	//				default:
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT
-	//				}
-	//				startDate = sTime.AddDate(0, 0, -limitDay).Format(utils.FormatDate)
-	//			}
-	//		}
-	//	}
-	//
-	//	result, err := RefreshEdbCalculateData(edbInfo.EdbInfoId, edbInfo.EdbCode, startDate)
-	//	if err != nil {
-	//		fmt.Println(v, "RefreshEdbCalculateData err", time.Now())
-	//		errmsg = "RefreshEdbCalculateData Err:" + err.Error()
-	//		return err
-	//	}
-	//	if result.Ret != 200 {
-	//		fmt.Println(v, "RefreshEdbCalculateData err;msg:", result.Msg, ";errMsg:", result.ErrMsg)
-	//		errmsg = fmt.Sprint(v, "RefreshEdbCalculateData err;msg:", result.Msg, ";errMsg:", result.ErrMsg)
-	//		return fmt.Errorf("刷新失败")
-	//	}
-	//}
-	//
-	////刷新相关预测计算指标
-	//for _, v := range predictCalculateArr {
-	//	edbInfo := newPredictCalculateMap[v]
-	//	if edbInfo == nil {
-	//		return err
-	//	}
-	//	startDate = edbInfo.StartDate
-	//	source := edbInfo.Source
-	//	if startDate == "" || startDate == "0000-00-00" { //如果没有开始日期,说明还没有计算出来数据,那么就往前面推40年吧(也意味着重新计算了)
-	//		startDate = time.Now().AddDate(-40, 0, 0).Format(utils.FormatDate)
-	//	} else {
-	//		if source == utils.DATA_SOURCE_PREDICT_CALCULATE {
-	//			startDate = ``
-	//			if refreshAll { //刷新所有数据,用开始时间作为起始日期去刷新
-	//				startDate = edbInfo.StartDate
-	//			} else {
-	//				sTime, err := time.Parse(utils.FormatDate, edbInfo.EndDate)
-	//				if err != nil {
-	//					return err
-	//				}
-	//				frequency := edbInfo.Frequency
-	//				var limitDay int
-	//				switch frequency {
-	//				case "日度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT
-	//				case "周度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 7
-	//				case "月度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 30
-	//				case "季度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 90
-	//				case "年度":
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT * 365
-	//				default:
-	//					limitDay = utils.DATA_START_REFRESH_LIMIT
-	//				}
-	//				startDate = sTime.AddDate(0, 0, -limitDay).Format(utils.FormatDate)
-	//			}
-	//		}
-	//	}
-	//
-	//	result, err := RefreshPredictEdbCalculateData(edbInfo.EdbInfoId, edbInfo.EdbCode, startDate)
-	//	if err != nil {
-	//		fmt.Println(v, "RefreshEdbCalculateData err", time.Now())
-	//		errmsg = "RefreshEdbCalculateData Err:" + err.Error()
-	//		return err
-	//	}
-	//	if result.Ret != 200 {
-	//		fmt.Println(v, "RefreshEdbCalculateData err;msg:", result.Msg, ";errMsg:", result.ErrMsg)
-	//		errmsg = fmt.Sprint(v, "RefreshEdbCalculateData err;msg:", result.Msg, ";errMsg:", result.ErrMsg)
-	//		return fmt.Errorf("刷新失败")
-	//	}
-	//}
-	//return err
 }
 
-// EdbInfoRefreshAllFromBaseV3 全部刷新指标(切换到edb_lib服务)
+// EdbInfoRefreshAllFromBaseV3Bak 全部刷新指标(切换到edb_lib服务)
 // @author Roc
 // @datetime 2022-09-16 11:04:44
 // @description 将原有的单个指标刷新,调整为批量多个指标刷新
-func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (err error, isAsync bool) {
+// DeprecatedTime 2023-10-23 09:38:19废弃
+// Deprecated
+func EdbInfoRefreshAllFromBaseV3Bak(edbInfoIdList []int, refreshAll, isSync bool) (err error, isAsync bool) {
 	var errmsg string
 	defer func() {
 		if err != nil {
@@ -245,6 +66,155 @@ func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (
 	return
 }
 
+// EdbInfoRefreshAllFromBaseV3
+//
+//	@Description: 全部刷新指标(切换到edb_lib服务)
+//	@author: Roc
+//	@datetime2023-10-23 09:57:55
+//	@param edbInfoIdList []int
+//	@param refreshAll bool
+//	@param isSync bool
+//	@return err error
+//	@return isAsync bool
+func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (err error, isAsync bool) {
+	var errmsg string
+	defer func() {
+		if err != nil {
+			fmt.Println("EdbInfoRefreshAllFromBaseV3 Err:" + err.Error() + ";errmsg:" + errmsg)
+			go alarm_msg.SendAlarmMsg("EdbInfoRefreshAllFromBaseV3,Err"+err.Error()+";errMsg:"+errmsg, 3)
+		}
+	}()
+	traceEdbInfoList, err := TraceEdbInfoByEdbInfoIdList(edbInfoIdList)
+	if err != nil {
+		return
+	}
+	// existEdbInfoIdMap 已经处理了的指标id map
+	existEdbInfoIdMap := make(map[int]int)
+
+	// 基础指标
+	newBaseEdbInfoArr := make([]*data_manage.EdbInfo, 0)
+	newBasePredictEdbInfoArr := make([]*data_manage.EdbInfo, 0)
+	newBaseMap := make(map[int]*data_manage.EdbInfo)
+	newPredictBaseMap := make(map[int]*data_manage.EdbInfo)
+
+	// 计算指标
+	newCalculateMap := make(map[int]*data_manage.EdbInfo)
+	newPredictCalculateMap := make(map[int]*data_manage.EdbInfo)
+	calculateArr := make([]int, 0)
+	predictCalculateArr := make([]int, 0)
+
+	// 获取关联指标
+	for _, traceEdbInfo := range traceEdbInfoList {
+		tmpBaseEdbInfoArr, tmpBasePredictEdbInfoArr, tmpCalculateMap, tmpPredictCalculateMap, _, _ := getRefreshEdbInfoListByTraceEdbInfo(traceEdbInfo, existEdbInfoIdMap)
+
+		// 普通基础指标
+		for _, edbInfo := range tmpBaseEdbInfoArr {
+			if _, ok := newBaseMap[edbInfo.EdbInfoId]; !ok {
+				newBaseMap[edbInfo.EdbInfoId] = edbInfo
+				newBaseEdbInfoArr = append(newBaseEdbInfoArr, edbInfo)
+			}
+		}
+
+		// 预测基础指标
+		for _, edbInfo := range tmpBasePredictEdbInfoArr {
+			if _, ok := newPredictBaseMap[edbInfo.EdbInfoId]; !ok {
+				newPredictBaseMap[edbInfo.EdbInfoId] = edbInfo
+				newBasePredictEdbInfoArr = append(newBasePredictEdbInfoArr, edbInfo)
+			}
+		}
+
+		// 普通计算指标
+		for _, edbInfo := range tmpCalculateMap {
+			if _, ok := newCalculateMap[edbInfo.EdbInfoId]; !ok {
+				newCalculateMap[edbInfo.EdbInfoId] = edbInfo
+				calculateArr = append(calculateArr, edbInfo.EdbInfoId)
+			}
+		}
+
+		// 预测计算指标
+		for _, edbInfo := range tmpPredictCalculateMap {
+			if _, ok := newPredictCalculateMap[edbInfo.EdbInfoId]; !ok {
+				newPredictCalculateMap[edbInfo.EdbInfoId] = edbInfo
+				predictCalculateArr = append(predictCalculateArr, edbInfo.EdbInfoId)
+			}
+		}
+	}
+
+	// 普通计算指标的id
+	sort.Ints(calculateArr)
+	// 预测计算指标的id
+	sort.Ints(predictCalculateArr)
+
+	// 需要刷新的指标数量
+	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
+
+	if totalEdbInfo <= 20 || isSync {
+		err = edbInfoRefreshAll(refreshAll, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
+	} else {
+		isAsync = true
+		go edbInfoRefreshAll(refreshAll, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
+	}
+
+	return
+}
+
+// getRefreshEdbInfoListByTraceEdbInfo根据溯源获取关联指标
+func getRefreshEdbInfoListByTraceEdbInfo(traceEdbInfo data_manage.TraceEdbInfoResp, existEdbInfoIdMap map[int]int) (newBaseEdbInfoArr, newBasePredictEdbInfoArr []*data_manage.EdbInfo, newCalculateMap, newPredictCalculateMap map[int]*data_manage.EdbInfo, calculateArr, predictCalculateArr []int) {
+	newBaseEdbInfoArr = make([]*data_manage.EdbInfo, 0)
+	newBasePredictEdbInfoArr = make([]*data_manage.EdbInfo, 0)
+	newCalculateMap = make(map[int]*data_manage.EdbInfo)
+	newPredictCalculateMap = make(map[int]*data_manage.EdbInfo)
+	calculateArr = make([]int, 0)
+	predictCalculateArr = make([]int, 0)
+
+	_, ok := existEdbInfoIdMap[traceEdbInfo.EdbInfoId]
+	if ok {
+		return
+	}
+
+	existEdbInfoIdMap[traceEdbInfo.EdbInfoId] = traceEdbInfo.EdbInfoId
+
+	switch traceEdbInfo.EdbInfoType {
+	//0-普通指标; 1-预测指标
+	case 0: // 0-普通指标
+		if traceEdbInfo.EdbType == 1 { //1-基础指标
+			newBaseEdbInfoArr = append(newBaseEdbInfoArr, traceEdbInfo.EdbInfo)
+		} else if traceEdbInfo.EdbType == 2 { //2-计算指标
+			newCalculateMap[traceEdbInfo.EdbInfoId] = traceEdbInfo.EdbInfo
+			calculateArr = append(calculateArr, traceEdbInfo.EdbInfoId)
+		}
+	case 1: // 1-预测指标
+		if traceEdbInfo.EdbType == 1 { //1-基础指标
+			newBasePredictEdbInfoArr = append(newBasePredictEdbInfoArr, traceEdbInfo.EdbInfo)
+		} else if traceEdbInfo.EdbType == 2 { //2-计算指标
+			newPredictCalculateMap[traceEdbInfo.EdbInfoId] = traceEdbInfo.EdbInfo
+			predictCalculateArr = append(predictCalculateArr, traceEdbInfo.EdbInfoId)
+		}
+	}
+
+	if traceEdbInfo.Child != nil && len(traceEdbInfo.Child) > 0 {
+		for _, v := range traceEdbInfo.Child {
+			tmpBaseEdbInfoArr, tmpPredictEdbInfoArr, tmpCalculateMap, tmpPredictCalculateMap, tmpCalculateArr, tmpPredictCalculateArr := getRefreshEdbInfoListByTraceEdbInfo(v, existEdbInfoIdMap)
+
+			newBaseEdbInfoArr = append(newBaseEdbInfoArr, tmpBaseEdbInfoArr...)
+			newBasePredictEdbInfoArr = append(newBasePredictEdbInfoArr, tmpPredictEdbInfoArr...)
+
+			for k, tmpEdbInfo := range tmpCalculateMap {
+				newCalculateMap[k] = tmpEdbInfo
+			}
+
+			for k, tmpEdbInfo := range tmpPredictCalculateMap {
+				newPredictCalculateMap[k] = tmpEdbInfo
+			}
+
+			calculateArr = append(calculateArr, tmpCalculateArr...)
+			predictCalculateArr = append(predictCalculateArr, tmpPredictCalculateArr...)
+		}
+	}
+
+	return
+}
+
 func edbInfoRefreshAll(refreshAll bool, newBaseEdbInfoArr, newBasePredictEdbInfoArr []*data_manage.EdbInfo, newCalculateMap, newPredictCalculateMap map[int]*data_manage.EdbInfo, calculateArr, predictCalculateArr []int) (err error) {
 	var errmsg string
 	defer func() {
@@ -2572,9 +2542,15 @@ func EdbInfoAdd(source, classifyId int, edbCode, edbName, frequency, unit, start
 
 	sourceName, ok := sourceNameMap[source]
 	if !ok {
-		errMsg = "指标来源异常"
-		err = errors.New(errMsg)
-		return
+		edbSource := data_manage.EdbSourceIdMap[source]
+		if edbSource != nil {
+			sourceName = edbSource.SourceName
+		}
+		if sourceName == "" {
+			errMsg = "指标来源异常"
+			err = errors.New(errMsg)
+			return
+		}
 	}
 	edbInfo.SourceName = sourceName
 
@@ -2690,6 +2666,7 @@ func TraceEdbInfoByEdbInfoId(edbInfoId int) (traceEdbInfo data_manage.TraceEdbIn
 		//Source:      edbInfo.Source,
 		UniqueCode: edbInfo.UniqueCode,
 		ClassifyId: edbInfo.ClassifyId,
+		EdbInfo:    edbInfo,
 	}
 	findIdMap := make(map[int]int)
 	findIdMap[edbInfoId] = edbInfoId
@@ -2712,6 +2689,55 @@ func TraceEdbInfoByEdbInfoId(edbInfoId int) (traceEdbInfo data_manage.TraceEdbIn
 	return
 }
 
+// TraceEdbInfoByEdbInfoIdList 指标追溯
+func TraceEdbInfoByEdbInfoIdList(edbInfoIdList []int) (traceEdbInfoList []data_manage.TraceEdbInfoResp, err error) {
+	traceEdbInfoList = make([]data_manage.TraceEdbInfoResp, 0)
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(edbInfoIdList)
+	if err != nil {
+		return
+	}
+	edbInfoRuleMap := make(map[int]string, 0)
+	edbMappingMap := make(map[int][]*data_manage.EdbInfoCalculateMappingInfo)
+
+	findIdMap := make(map[int]int)
+	existMap := make(map[int]data_manage.TraceEdbInfoResp)
+
+	for _, edbInfo := range edbInfoList {
+		findIdMap[edbInfo.EdbInfoId] = edbInfo.EdbInfoId
+		//edbInfoRuleMap[edbInfoId] = getEdbRuleTitle(edbInfo)
+		traceEdbInfo := data_manage.TraceEdbInfoResp{
+			//EdbInfoId: edbInfo.EdbInfoId,
+			EdbInfoId:   edbInfo.EdbInfoId,
+			EdbInfoType: edbInfo.EdbInfoType,
+			EdbName:     edbInfo.EdbName,
+			EdbType:     edbInfo.EdbType,
+			//Source:      edbInfo.Source,
+			UniqueCode: edbInfo.UniqueCode,
+			ClassifyId: edbInfo.ClassifyId,
+			EdbInfo:    edbInfo,
+		}
+		traceEdbInfo.Child, err = traceEdbInfoByEdbInfoId(edbInfo.EdbInfoId, traceEdbInfo, edbInfoRuleMap, findIdMap, existMap, edbMappingMap)
+		traceEdbInfoList = append(traceEdbInfoList, traceEdbInfo)
+	}
+
+	//findEdbInfoIdList := make([]int, 0)
+	//for _, v := range findIdMap {
+	//	findEdbInfoIdList = append(findEdbInfoIdList, v)
+	//}
+	//findEdbInfoList, err := data_manage.GetEdbInfoByIdList(findEdbInfoIdList)
+	//if err != nil {
+	//	return
+	//}
+	//edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	//for _, tmpEdbInfo := range findEdbInfoList {
+	//	edbInfoMap[tmpEdbInfo.EdbInfoId] = tmpEdbInfo
+	//}
+	//for k, traceEdbInfo := range traceEdbInfoList {
+	//	traceEdbInfoList[k], err = handleTraceEdbInfo(traceEdbInfo, 0, edbInfoMap, edbMappingMap)
+	//}
+	return
+}
+
 // traceEdbInfoByEdbInfoId 指标追溯
 func traceEdbInfoByEdbInfoId(edbInfoId int, traceEdbInfo data_manage.TraceEdbInfoResp, edbInfoRuleMap map[int]string, findIdMap map[int]int, existMap map[int]data_manage.TraceEdbInfoResp, edbMappingMap map[int][]*data_manage.EdbInfoCalculateMappingInfo) (child []data_manage.TraceEdbInfoResp, err error) {
 	traceEdbInfo, ok := existMap[edbInfoId]
@@ -2725,8 +2751,26 @@ func traceEdbInfoByEdbInfoId(edbInfoId int, traceEdbInfo data_manage.TraceEdbInf
 		err = fmt.Errorf("GetEdbInfoCalculateMappingListByEdbInfoId err: %s", e.Error())
 		return
 	}
-	edbMappingMap[edbInfoId] = edbInfoMappingList
 
+	// 指标信息map
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	if len(edbInfoMappingList) > 0 {
+		fromEdbInfoIdList := make([]int, 0)
+		for _, v := range edbInfoMappingList {
+			fromEdbInfoIdList = append(fromEdbInfoIdList, v.FromEdbInfoId)
+		}
+		edbInfoList, tmpErr := data_manage.GetEdbInfoByIdList(fromEdbInfoIdList)
+		if tmpErr != nil {
+			err = fmt.Errorf("traceEdbInfoByEdbInfoId GetEdbInfoByIdList err: %s", tmpErr.Error())
+			return
+		}
+		for _, v := range edbInfoList {
+			edbInfoMap[v.EdbInfoId] = v
+		}
+
+	}
+
+	edbMappingMap[edbInfoId] = edbInfoMappingList
 	for _, v := range edbInfoMappingList {
 		tmpEdbInfoId := v.FromEdbInfoId
 		tmpTraceEdbInfo := data_manage.TraceEdbInfoResp{
@@ -2735,6 +2779,7 @@ func traceEdbInfoByEdbInfoId(edbInfoId int, traceEdbInfo data_manage.TraceEdbInf
 			EdbType:     v.FromEdbType,
 			UniqueCode:  v.FromUniqueCode,
 			ClassifyId:  v.FromClassifyId,
+			EdbInfo:     edbInfoMap[v.FromEdbInfoId],
 		}
 
 		// 计算指标/预测指标继续溯源

+ 5 - 0
services/data/edb_info_calculate.go

@@ -685,6 +685,11 @@ func handleDataByLinearRegression(edbInfoDataList []*data_manage.EdbDataList, ha
 	return
 }
 
+// HandleDataByLinearRegression 插值法补充数据(线性方程式)
+func HandleDataByLinearRegression(edbInfoDataList []*data_manage.EdbDataList, handleDataMap map[string]float64) (err error) {
+	return handleDataByLinearRegression(edbInfoDataList, handleDataMap)
+}
+
 // CallCalculateComputeCorrelation 调用计算拟合残差的相关系数
 func CallCalculateComputeCorrelation(data *data_manage.EdbInfoCalculateBatchSaveReqByEdbLib) (val string, err error, errMsg string) {
 	errMsg = "计算失败"

+ 426 - 0
services/data/excel/custom_analysis.go

@@ -0,0 +1,426 @@
+package excel
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"strconv"
+	"time"
+)
+
+var cellSplitNum = 10000 // 基础分割单元格数
+
+// AddCustomAnalysisTable 添加自定义分析表格
+func AddCustomAnalysisTable(excelName, content, excelImage string, excelClassifyId int, sysUser *system.Admin) (excelInfo *excel.ExcelInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	contentByte := []byte(content)
+
+	var luckySheetList []LuckySheet
+
+	err = json.Unmarshal(contentByte, &luckySheetList)
+	if err != nil {
+		return
+	}
+
+	// sheet内容为空
+	if len(luckySheetList) <= 0 {
+		errMsg = "sheet内容为空"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	excelClassify, err := excel.GetExcelClassifyById(excelClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if excelClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	if excelClassify.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		errMsg = "当前分类不是自定义分析分类"
+		err = errors.New("当前分类不是自定义分析分类")
+		isSendEmail = false
+		return
+	}
+
+	// 校验是否同名文件
+	{
+		var condition string
+		var pars []interface{}
+		condition += " AND excel_classify_id=? "
+		pars = append(pars, excelClassifyId)
+
+		condition += " AND excel_name=? "
+		pars = append(pars, excelName)
+
+		// 获取分类下是否存在该表格名称
+		count, tmpErr := excel.GetExcelInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断表格名称是否存在失败"
+			err = errors.New("判断表格名称是否存在失败,Err:" + tmpErr.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "表格名称已存在,请重新填写表格名称"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	// 表格
+	excelInfo = &excel.ExcelInfo{
+		//ExcelInfoId:     0,
+		ExcelName: excelName,
+		Source:    utils.CUSTOM_ANALYSIS_TABLE,
+		//ExcelType:       req.ExcelType,
+		UniqueCode:      utils.MD5(utils.EXCEL_DATA_PREFIX + "_" + timestamp),
+		ExcelClassifyId: excelClassifyId,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		Content:         ``,
+		ExcelImage:      excelImage,
+		Sort:            0,
+		IsDelete:        0,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+
+	addSheetList := make([]excel.AddExcelSheetParams, 0)
+
+	// sheet处理
+	sheetNameMap := make(map[string]string)
+	for k, sheetInfo := range luckySheetList {
+		sheetName := utils.TrimLRStr(sheetInfo.Name)
+		_, ok := sheetNameMap[sheetName]
+		if ok {
+			errMsg = "excel表中存在同名sheet"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+
+		sheetConf, tmpErr := json.Marshal(sheetInfo.Config)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 计算公式
+		sheetCalcChain, tmpErr := json.Marshal(sheetInfo.CalcChain)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		addSheetItem := excel.AddExcelSheetParams{
+			ExcelSheetId: 0,
+			ExcelInfoId:  0,
+			SheetName:    sheetName,
+			Index:        sheetInfo.Index,
+			Sort:         k,
+			Config:       string(sheetConf),
+			CalcChain:    string(sheetCalcChain),
+		}
+
+		lenCellData := len(sheetInfo.CellData)
+
+		splitLen := lenCellData / cellSplitNum
+		residue := lenCellData % cellSplitNum
+		if residue > 0 {
+			splitLen += 1
+		}
+
+		sheetDataList := make([]*excel.ExcelSheetData, 0)
+		for i := 0; i < splitLen; i++ {
+
+			startRow := i * cellSplitNum
+			endRow := (i + 1) * cellSplitNum
+			if i == splitLen-1 && residue > 0 {
+				endRow = lenCellData
+			}
+
+			tmpData := sheetInfo.CellData[startRow:endRow]
+			tmpDataByte, tmpErr := json.Marshal(tmpData)
+			if tmpErr != nil {
+				errMsg = "保存失败"
+				err = errors.New("保存失败:" + tmpErr.Error())
+				return
+			}
+			sheetDataList = append(sheetDataList, &excel.ExcelSheetData{
+				ExcelDataId:  0,
+				ExcelInfoId:  0,
+				ExcelSheetId: 0,
+				Sort:         i + 1,
+				Data:         string(tmpDataByte),
+				ModifyTime:   time.Now(),
+				CreateTime:   time.Now(),
+			})
+		}
+		addSheetItem.DataList = sheetDataList
+
+		addSheetList = append(addSheetList, addSheetItem)
+	}
+
+	err = excel.AddExcelInfoAndSheet(excelInfo, addSheetList)
+
+	return
+}
+
+// SaveCustomAnalysisTable 编辑自定义分析表格
+func SaveCustomAnalysisTable(excelInfo *excel.ExcelInfo, excelName, content, excelImage string, excelClassifyId int, sheetOpList []request.SheetOp) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	contentByte := []byte(content)
+
+	var luckySheetList []LuckySheet
+
+	err = json.Unmarshal(contentByte, &luckySheetList)
+	if err != nil {
+		return
+	}
+
+	// sheet内容为空
+	if len(luckySheetList) <= 0 {
+		errMsg = "sheet内容为空"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	// sheet内容为空
+	//if len(sheetOpList) <= 0 {
+	//	errMsg = "sheet操作为空"
+	//	err = errors.New(errMsg)
+	//	isSendEmail = false
+	//	return
+	//}
+
+	excelClassify, err := excel.GetExcelClassifyById(excelClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if excelClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	if excelClassify.Source != utils.CUSTOM_ANALYSIS_TABLE {
+		errMsg = "当前分类不是自定义分析分类"
+		err = errors.New("当前分类不是自定义分析分类")
+		isSendEmail = false
+		return
+	}
+
+	// 校验是否同名文件
+	{
+		var condition string
+		var pars []interface{}
+		condition += " AND excel_classify_id=?  AND excel_info_id !=?  "
+		pars = append(pars, excelClassifyId, excelInfo.ExcelInfoId)
+
+		condition += " AND excel_name=? "
+		pars = append(pars, excelName)
+
+		// 获取分类下是否存在该表格名称
+		count, tmpErr := excel.GetExcelInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断表格名称是否存在失败"
+			err = errors.New("判断表格名称是否存在失败,Err:" + tmpErr.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "表格名称已存在,请重新填写表格名称"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	//// 查找当前excel的sheet列表
+	//currSheetList, err := excel.GetAllSheetList(excelInfo.ExcelInfoId)
+	//if err != nil {
+	//	errMsg = "保存失败"
+	//	err = errors.New("查找当前excel的sheet列表失败,Err:" + err.Error())
+	//	return
+	//}
+	//currSheetMap := make(map[string]string)
+	//for _, currSheet := range currSheetList {
+	//	currSheetMap[currSheet.SheetName] = currSheet.SheetName
+	//}
+	//
+	//for k, sheetOp := range sheetOpList {
+	//	sheetName := utils.TrimLRStr(sheetOp.SheetName)
+	//	switch sheetOp.OpType {
+	//	case "add":
+	//		// 新增
+	//		_, ok := currSheetMap[sheetName]
+	//		if ok {
+	//			errMsg = "存在同名sheet:" + sheetName
+	//			err = errors.New(errMsg)
+	//			isSendEmail = false
+	//			return
+	//		}
+	//	case "replace":
+	//		// 替换
+	//	case "append":
+	//		// 追加
+	//	default:
+	//		errMsg = fmt.Sprint("第", k+1, "个sheet,错误的操作类型")
+	//		err = errors.New(errMsg + "op:" + sheetOp.OpType)
+	//		isSendEmail = false
+	//		return
+	//	}
+	//}
+
+	// 表格
+	excelInfo.ExcelName = excelName
+	// 如果分类不传入的话,那么分类不变更
+	if excelClassifyId <= 0 {
+		excelInfo.ExcelClassifyId = excelClassifyId
+	}
+	// 如果缩略图不传入的话,那么缩略图不变更
+	if excelImage != `` {
+		excelInfo.ExcelImage = excelImage
+	}
+	excelInfo.ModifyTime = time.Now()
+	updateExcelInfoParam := []string{"ExcelName", "ExcelClassifyId", "ExcelImage", "ModifyTime"}
+
+	addSheetList := make([]excel.AddExcelSheetParams, 0)
+
+	// sheet处理
+	sheetNameMap := make(map[string]string)
+	for k, sheetInfo := range luckySheetList {
+		sheetName := utils.TrimLRStr(sheetInfo.Name)
+		_, ok := sheetNameMap[sheetName]
+		if ok {
+			errMsg = "excel表中存在同名sheet"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+
+		sheetConf, tmpErr := json.Marshal(sheetInfo.Config)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 计算公式
+		sheetCalcChain, tmpErr := json.Marshal(sheetInfo.CalcChain)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		addSheetItem := excel.AddExcelSheetParams{
+			ExcelSheetId: 0,
+			ExcelInfoId:  excelInfo.ExcelInfoId,
+			SheetName:    sheetName,
+			Index:        sheetInfo.Index,
+			Sort:         k,
+			Config:       string(sheetConf),
+			CalcChain:    string(sheetCalcChain),
+		}
+
+		lenCellData := len(sheetInfo.CellData)
+
+		splitLen := lenCellData / cellSplitNum
+		residue := lenCellData % cellSplitNum
+		if residue > 0 {
+			splitLen += 1
+		}
+
+		sheetDataList := make([]*excel.ExcelSheetData, 0)
+		for i := 0; i < splitLen; i++ {
+
+			startRow := i * cellSplitNum
+			endRow := (i + 1) * cellSplitNum
+			if i == splitLen-1 && residue > 0 {
+				endRow = lenCellData
+			}
+
+			tmpData := sheetInfo.CellData[startRow:endRow]
+			tmpDataByte, tmpErr := json.Marshal(tmpData)
+			if tmpErr != nil {
+				errMsg = "保存失败"
+				err = errors.New("保存失败:" + tmpErr.Error())
+				return
+			}
+			sheetDataList = append(sheetDataList, &excel.ExcelSheetData{
+				ExcelDataId:  0,
+				ExcelInfoId:  excelInfo.ExcelInfoId,
+				ExcelSheetId: 0,
+				Sort:         i + 1,
+				Data:         string(tmpDataByte),
+				ModifyTime:   time.Now(),
+				CreateTime:   time.Now(),
+			})
+		}
+		addSheetItem.DataList = sheetDataList
+
+		addSheetList = append(addSheetList, addSheetItem)
+	}
+
+	err = excel.SaveExcelInfoAndSheet(excelInfo, updateExcelInfoParam, addSheetList)
+
+	return
+}
+
+type LuckySheet struct {
+	Name string `json:"name"`
+	//Config struct {
+	//	Columnlen struct {
+	//		Num15 int `json:"15"`
+	//		Num16 int `json:"16"`
+	//		Num20 int `json:"20"`
+	//		Num34 int `json:"34"`
+	//		Num35 int `json:"35"`
+	//	} `json:"columnlen"`
+	//} `json:"config"`
+	Config           interface{}
+	Index            string               `json:"index"`
+	Order            int                  `json:"order"`
+	ZoomRatio        int                  `json:"zoomRatio"`
+	ShowGridLines    string               `json:"showGridLines"`
+	DefaultColWidth  int                  `json:"defaultColWidth"`
+	DefaultRowHeight int                  `json:"defaultRowHeight"`
+	CellData         []LuckySheetCellData `json:"celldata"`
+	CalcChain        []interface{}        `json:"calcChain"`
+	//DataVerification struct {
+	//} `json:"dataVerification"`
+	//Hyperlink struct {
+	//} `json:"hyperlink"`
+	//Hide int `json:"hide"`
+}
+
+// LuckySheetCellData 单元格数据
+type LuckySheetCellData struct {
+	R int         `json:"r"`
+	C int         `json:"c"`
+	V interface{} `json:"v,omitempty"`
+}

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

@@ -0,0 +1,547 @@
+package excel
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/services/data"
+	excelServices "eta/eta_api/services/excel"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/araddon/dateparse"
+	"github.com/shopspring/decimal"
+	"github.com/xuri/excelize/v2"
+	"strings"
+)
+
+// GetCustomAnalysisExcelData 获取自定义分析的表格data数据
+func GetCustomAnalysisExcelData(excelInfo *excel.ExcelInfo) (luckySheet excelServices.LuckySheet, err error, errMsg string) {
+	// 查找当前excel的sheet列表
+	sheetList, err := excel.GetAllSheetList(excelInfo.ExcelInfoId)
+	if err != nil {
+		errMsg = "保存失败"
+		err = errors.New("查找当前excel的sheet列表失败,Err:" + err.Error())
+		return
+	}
+	currSheetMap := make(map[string]string)
+	for _, sheet := range sheetList {
+		currSheetMap[sheet.SheetName] = sheet.SheetName
+	}
+
+	sheetCellDataMapList := make(map[int][]excelServices.LuckySheetCellData)
+
+	// 通过excel的id获取各个sheet的单元格数据map
+	{
+		dataList, tmpErr := excel.GetAllSheetDataListByExcelInfoId(excelInfo.ExcelInfoId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		for _, cellData := range dataList {
+			sheetDataList, ok := sheetCellDataMapList[cellData.ExcelSheetId]
+			if !ok {
+				sheetDataList = make([]excelServices.LuckySheetCellData, 0)
+			}
+
+			tmpSheetDataList := make([]excelServices.LuckySheetCellData, 0)
+			err = json.Unmarshal([]byte(cellData.Data), &tmpSheetDataList)
+			if err != nil {
+				return
+			}
+			sheetCellDataMapList[cellData.ExcelSheetId] = append(sheetDataList, tmpSheetDataList...)
+		}
+	}
+
+	// 转成luckySheet的数据格式
+	luckySheet = excelServices.LuckySheet{
+		SheetList: make([]excelServices.LuckySheetData, 0),
+	}
+
+	for _, sheet := range sheetList {
+		var luckySheetDataConfig excelServices.LuckySheetDataConfig
+		err = json.Unmarshal([]byte(sheet.Config), &luckySheetDataConfig)
+		if err != nil {
+			return
+		}
+		tmpLuckySheetDataInfo := excelServices.LuckySheetData{
+			Name:     sheet.SheetName,
+			Index:    sheet.Sort,
+			CellData: sheetCellDataMapList[sheet.ExcelSheetId],
+			Config:   luckySheetDataConfig,
+		}
+		luckySheet.SheetList = append(luckySheet.SheetList, tmpLuckySheetDataInfo)
+	}
+
+	return
+}
+
+// GenerateExcelCustomAnalysisExcel 根据自定义分析的表格data数据生成excel
+func GenerateExcelCustomAnalysisExcel(excelInfo *excel.ExcelInfo) (downloadFilePath string, err error, errMsg string) {
+	luckySheet, err, errMsg := GetCustomAnalysisExcelData(excelInfo)
+	if err != nil {
+		return
+	}
+
+	downloadFilePath, err = luckySheet.ToExcel(false)
+
+	return
+}
+
+// HandleEdbSequenceVal 处理日期集和数据集(获取可用的日期、数据集)
+func HandleEdbSequenceVal(dateSequenceVal, dataSequenceVal []string) (newDateList []string, newDataList []float64, err error, errMsg string) {
+	newDateList = make([]string, 0)
+	newDataList = make([]float64, 0)
+
+	// 数据集
+	type dataStruct struct {
+		Value float64
+		Ok    bool
+	}
+	dataList := make([]dataStruct, 0)
+	{
+		var startData bool
+		for _, v := range dataSequenceVal {
+			// 如果没有数据集,那么就过滤
+			if v == `` {
+				// 如果开始插入数据了,那么就需要插入不存在值
+				if startData {
+					dataList = append(dataList, dataStruct{
+						Value: 0,
+						Ok:    false,
+					})
+				}
+				continue
+			}
+
+			v = strings.Replace(v, ",", "", -1)
+			v = strings.Replace(v, ",", "", -1)
+			// 过滤空格
+			v = strings.Replace(v, " ", "", -1)
+			tmpValDec, tmpErr := decimal.NewFromString(v)
+			if tmpErr != nil {
+				if startData {
+					dataList = append(dataList, dataStruct{
+						Value: 0,
+						Ok:    false,
+					})
+				}
+				continue
+			}
+			startData = true
+
+			tmpVal, _ := tmpValDec.Float64()
+			dataList = append(dataList, dataStruct{
+				Value: tmpVal,
+				Ok:    true,
+			})
+		}
+	}
+
+	// 日期集
+	dateList := make([]string, 0)
+	{
+		var startData bool
+		for _, v := range dateSequenceVal {
+			// 如果没有数据集,那么就过滤
+			if v == `` {
+				// 如果开始插入数据了,那么就需要插入不存在值
+				if startData {
+					dateList = append(dateList, "")
+				}
+				continue
+			}
+
+			// 过滤空格
+			v = strings.Replace(v, " ", "", -1)
+			t1, tmpErr := dateparse.ParseAny(v)
+			if tmpErr != nil {
+				if startData {
+					dateList = append(dateList, "")
+				}
+				continue
+			}
+			startData = true
+
+			dateList = append(dateList, t1.Format(utils.FormatDate))
+		}
+	}
+
+	lenData := len(dataList)
+	lenDate := len(dateList)
+
+	// 最小个数
+	num := lenDate
+	if num > lenData {
+		num = lenData
+	}
+
+	for i := 0; i < num; i++ {
+		date := dateList[i]
+		data := dataList[i]
+
+		// 日期为空、数据为空
+		if !data.Ok || date == `` {
+			continue
+		}
+
+		newDateList = append(newDateList, date)
+		newDataList = append(newDataList, data.Value)
+	}
+
+	return
+}
+
+// Refresh  刷新表格关联的指标信息
+func Refresh(excelInfo *excel.ExcelInfo) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	list, err := excel.GetAllExcelEdbMappingItemByExcelInfoId(excelInfo.ExcelInfoId)
+	if err != nil {
+		errMsg = "获取失败"
+		return
+	}
+
+	// 没有关联指标,那么就退出吧
+	if len(list) <= 0 {
+		return
+	}
+
+	for k, v := range list {
+		var tmpCalculateFormula excel.CalculateFormula
+		err = json.Unmarshal([]byte(v.CalculateFormula), &tmpCalculateFormula)
+		if err != nil {
+			errMsg = "获取失败"
+			err = errors.New("公式转换失败,Err:" + err.Error())
+			return
+		}
+		v.DateSequenceStr = tmpCalculateFormula.DateSequenceStr
+		v.DataSequenceStr = tmpCalculateFormula.DataSequenceStr
+		list[k] = v
+	}
+
+	luckySheet, err, errMsg := GetCustomAnalysisExcelData(excelInfo)
+	if err != nil {
+		return
+	}
+
+	// 获取excel表格数据
+	xlsxFile, err := luckySheet.GetExcelData(false)
+	if err != nil {
+		return
+	}
+
+	//fmt.Println(xlsxFile)
+
+	// ResetCustomAnalysisData 数据重置的结构体
+	type ResetCustomAnalysisData struct {
+		EdbInfoId int
+		DateList  []string
+		DataList  []float64
+	}
+
+	for _, v := range list {
+		dateList := make([]string, 0)
+		dataList := make([]string, 0)
+
+		// 日期序列
+		{
+			sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(v.DateSequenceStr)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+
+			// 查找sheet页
+			sheetInfo, ok := xlsxFile.Sheet[sheetName]
+			if !ok {
+				errMsg = "找不到" + sheetName
+				err = errors.New(errMsg)
+				return
+			}
+
+			// 选择行的数据
+			if isRow {
+				// 开始列名、结束列
+				var startColumn, endColumn int
+				if isAll {
+					// 结束列(其实也就是整列的个数)
+					endColumn = len(sheetInfo.Cols) - 1
+				} else {
+					//startNum = startNum - 1
+					//endNum = endNum - 1
+
+					tmpStartColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+					if tmpErr != nil {
+						errMsg = "列名异常:" + startColumnName
+						err = errors.New(errMsg)
+						return
+					}
+
+					tmpEndColumn, tmpErr := excelize.ColumnNameToNumber(endColumnName)
+					if tmpErr != nil {
+						errMsg = "列名异常:" + endColumnName
+						err = errors.New(errMsg)
+						return
+					}
+					startColumn = tmpStartColumn - 1
+					endColumn = tmpEndColumn - 1
+				}
+
+				for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
+					currCell := sheetInfo.Cell(startNum, currColumn)
+					if currCell == nil {
+						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+						err = errors.New(errMsg)
+						return
+					}
+					dateList = append(dateList, currCell.Value)
+				}
+
+			} else if isColumn { // 选择列的数据
+				if isAll {
+					// 结束行(其实也就是整个sheet有多少行)
+					endNum = len(sheetInfo.Rows) - 1
+				} else {
+					startNum = startNum - 1
+					endNum = endNum - 1
+				}
+
+				startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+				if tmpErr != nil {
+					errMsg = "列名异常:" + startColumnName
+					err = errors.New(errMsg)
+					return
+				}
+				startColumn = startColumn - 1
+
+				for currRow := startNum; currRow <= endNum; currRow++ {
+					currCell := sheetInfo.Cell(currRow, startColumn)
+					if currCell == nil {
+						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+						err = errors.New(errMsg)
+						return
+					}
+					dateList = append(dateList, currCell.Value)
+				}
+			}
+
+		}
+
+		// 数据序列
+		{
+			sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(v.DataSequenceStr)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+
+			// 查找sheet页
+			sheetInfo, ok := xlsxFile.Sheet[sheetName]
+			if !ok {
+				errMsg = "找不到" + sheetName
+				err = errors.New(errMsg)
+				return
+			}
+
+			// 选择行的数据
+			if isRow {
+				// 开始列名、结束列
+				var startColumn, endColumn int
+				if isAll {
+					// 结束列(其实也就是整列的个数)
+					endColumn = len(sheetInfo.Cols) - 1
+				} else {
+					//startNum = startNum - 1
+					//endNum = endNum - 1
+
+					tmpStartColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+					if tmpErr != nil {
+						errMsg = "列名异常:" + startColumnName
+						err = errors.New(errMsg)
+						return
+					}
+
+					tmpEndColumn, tmpErr := excelize.ColumnNameToNumber(endColumnName)
+					if tmpErr != nil {
+						errMsg = "列名异常:" + endColumnName
+						err = errors.New(errMsg)
+						return
+					}
+					startColumn = tmpStartColumn - 1
+					endColumn = tmpEndColumn - 1
+				}
+
+				for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
+					currCell := sheetInfo.Cell(startNum, currColumn)
+					if currCell == nil {
+						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+						err = errors.New(errMsg)
+						return
+					}
+					dataList = append(dataList, currCell.Value)
+				}
+
+			} else if isColumn { // 选择列的数据
+				if isAll {
+					// 结束行(其实也就是整个sheet有多少行)
+					endNum = len(sheetInfo.Rows) - 1
+				} else {
+					startNum = startNum - 1
+					endNum = endNum - 1
+				}
+
+				startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+				if tmpErr != nil {
+					errMsg = "列名异常:" + startColumnName
+					err = errors.New(errMsg)
+					return
+				}
+				startColumn = startColumn - 1
+
+				for currRow := startNum; currRow <= endNum; currRow++ {
+					currCell := sheetInfo.Cell(currRow, startColumn)
+					if currCell == nil {
+						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+						err = errors.New(errMsg)
+						return
+					}
+					dataList = append(dataList, currCell.Value)
+				}
+			}
+
+		}
+
+		//fmt.Println("日期序列结束")
+
+		// 将excel中的日期、数据系列处理
+		relDateList, relDataList, tmpErr, tmpErrMsg := HandleEdbSequenceVal(dateList, dataList)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = tmpErrMsg
+			return
+		}
+		req2 := &ResetCustomAnalysisData{
+			EdbInfoId: v.EdbInfoId,
+			DateList:  relDateList,
+			DataList:  relDataList,
+		}
+
+		// 调用指标库去更新
+		reqJson, tmpErr := json.Marshal(req2)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		respItem, tmpErr := data.ResetCustomAnalysisData(string(reqJson))
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		if respItem.Ret != 200 {
+			errMsg = respItem.Msg
+			err = errors.New(respItem.ErrMsg)
+			return
+		}
+		//sheetInfo.Cell()
+	}
+
+	//xlsxFile.Sheet[]
+
+	return
+}
+
+// GetSheetStr
+// @return sheetName string 用户选择的sheet名称
+// @return startColumnName string 用户选择的开始列名称
+// @return endColumnName string 用户选择的结束列名称
+// @return startNum int 用户选择的开始列单元格位置
+// @return endNum int 用户选择的结束列单元格位置
+// @return isAll bool 是否选择整行/列数据
+// @return isRow bool 是否选择行数据
+// @return isColumn bool 是否选择列数据
+func GetSheetStr(sequenceStr string) (sheetName, startColumnName, endColumnName string, startNum, endNum int, isAll, isRow, isColumn bool, err error) {
+	// 找出sheetName
+	tmpList := strings.Split(sequenceStr, "!")
+	if len(tmpList) != 2 {
+		err = errors.New("错误的公式,查找sheet异常:" + sequenceStr)
+		return
+	}
+
+	sheetName = tmpList[0]
+
+	// 分离开始/结束单元格
+	tmpList = strings.Split(tmpList[1], ":")
+	if len(tmpList) != 2 {
+		err = errors.New("错误的公式,查找开始/结束单元格异常:" + sequenceStr)
+		return
+	}
+
+	startList := strings.Split(tmpList[0], "$")
+	endList := strings.Split(tmpList[1], "$")
+
+	lenList := len(startList)
+	if lenList != len(endList) {
+		err = errors.New("错误的公式,开始与结束单元格异常:" + sequenceStr)
+		return
+	}
+
+	if lenList != 3 && lenList != 2 {
+		err = errors.New("错误的公式:" + sequenceStr)
+		return
+	}
+
+	startColumnName = startList[1]
+	endColumnName = endList[1]
+
+	// 长度为2的话,那说明是整行或整列
+	if lenList == 2 {
+		isAll = true
+
+		startDeci, tmpErr1 := decimal.NewFromString(startList[1])
+		endDeci, tmpErr2 := decimal.NewFromString(endList[1])
+
+		if tmpErr1 == nil && tmpErr2 == nil {
+			isRow = true // 正常转换的话,那么就是整行
+			startNum = int(startDeci.IntPart())
+			endNum = int(endDeci.IntPart())
+			startColumnName = ``
+			endColumnName = ``
+
+			return
+		}
+
+		if tmpErr1 == nil || tmpErr2 == nil {
+			err = errors.New("错误的公式2:" + sequenceStr)
+			return
+		}
+
+		// 如果不能转成数字,那么就是整列
+		isColumn = true
+
+		return
+	}
+
+	// 确定行
+	startDeci, tmpErr1 := decimal.NewFromString(startList[2])
+	endDeci, tmpErr2 := decimal.NewFromString(endList[2])
+	if tmpErr1 != nil && tmpErr1 != tmpErr2 {
+		err = errors.New("错误的公式3:" + sequenceStr)
+		return
+	}
+
+	startNum = int(startDeci.IntPart())
+	endNum = int(endDeci.IntPart())
+
+	if startColumnName != endColumnName && startNum != endNum {
+		err = errors.New("选区不允许跨行或者跨列")
+	}
+
+	if startColumnName == endColumnName {
+		isColumn = true // 列数据
+	} else {
+		isRow = true // 行数据
+	}
+
+	return
+}

+ 20 - 13
services/data/excel_info.go → services/data/excel/excel_info.go

@@ -1,12 +1,14 @@
-package data
+package excel
 
 import (
 	"encoding/json"
 	"errors"
 	"eta/eta_api/models/data_manage"
-	"eta/eta_api/models/data_manage/request"
-	"eta/eta_api/models/data_manage/response"
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
+	"eta/eta_api/models/data_manage/excel/response"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/shopspring/decimal"
@@ -21,7 +23,7 @@ import (
 func GetExcelDetailInfoByExcelInfoId(excelInfoId int) (excelDetail response.ExcelInfoDetail, errMsg string, err error) {
 	errMsg = `获取失败`
 	//获取eta表格信息
-	excelInfo, err := data_manage.GetExcelInfoById(excelInfoId)
+	excelInfo, err := excel.GetExcelInfoById(excelInfoId)
 	if err != nil {
 		err = errors.New("获取ETA表格信息失败,Err:" + err.Error())
 		if err.Error() == utils.ErrNoRow() {
@@ -90,9 +92,6 @@ func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int) (butt
 	button.CopyButton = true
 	button.DownloadButton = true
 
-	if source == 1 {
-		button.OpButton = true
-	}
 
 	// 1、本用户创建的表格,可编辑、刷新、另存为、下载、删除,删除需二次确认;
 	// 2、管理员角色对所有表格有如上权限;
@@ -101,6 +100,14 @@ func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int) (butt
 		button.DeleteButton = true
 	}
 
+	// 自定义分析
+	if source == utils.CUSTOM_ANALYSIS_TABLE {
+		if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN || sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_FICC_ADMIN || sysUser.AdminId == belongUserId {
+			button.OpEdbButton = true      // 生成、查看指标按钮
+			button.RefreshEdbButton = true // 刷新指标按钮
+		}
+	}
+
 	return
 }
 
@@ -111,7 +118,7 @@ func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList [
 	case 0:
 		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.EdbInfoId, ``, ``)
 	case 1:
-		_, dataList, _, _, err, _ = GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
 	default:
 		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
 	}
@@ -212,7 +219,7 @@ func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resul
 	case 0:
 		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.EdbInfoId, ``, ``)
 	case 1:
-		_, dataList, _, _, err, _ = GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
 	default:
 		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
 	}
@@ -257,7 +264,7 @@ func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resul
 
 	// 插值法处理
 	handleDataMap := make(map[string]float64)
-	err = handleDataByLinearRegression(baseDataList, handleDataMap)
+	err = data.HandleDataByLinearRegression(baseDataList, handleDataMap)
 	if err != nil {
 		return
 	}
@@ -306,7 +313,7 @@ func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate s
 	case 0:
 		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.EdbInfoId, ``, endDate)
 	case 1:
-		_, dataList, _, _, err, _ = GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, endDate, true)
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, endDate, true)
 	default:
 		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
 	}
@@ -489,7 +496,7 @@ func GetTableDataConfig(reqData request.TableDataReq) (tableDataConfig TableData
 		case 0:
 			firstDataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.EdbInfoId, ``, ``)
 		case 1:
-			_, firstDataList, _, _, err, _ = GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+			_, firstDataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
 		default:
 			err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
 		}
@@ -1287,7 +1294,7 @@ func GetMixedTableCellData(config [][]request.MixedTableCellDataReq) (newMixedTa
 		case 0:
 			dataList, _ = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.EdbInfoId, ``, ``)
 		case 1:
-			_, dataList, _, _, _, _ = GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+			_, dataList, _, _, _, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
 		default:
 			err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
 		}

+ 275 - 0
services/data/excel/excel_op.go

@@ -0,0 +1,275 @@
+package excel
+
+import (
+	"errors"
+	excelModel "eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services"
+	"eta/eta_api/services/alarm_msg"
+	excel "eta/eta_api/services/excel"
+	"eta/eta_api/utils"
+	"fmt"
+	"os"
+	"strconv"
+	"time"
+)
+
+// Delete excel删除
+func Delete(excelInfo *excelModel.ExcelInfo, sysUser *system.Admin) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	// 操作权限校验
+	{
+		button := GetExcelInfoOpButton(sysUser, excelInfo.SysUserId, excelInfo.Source)
+		if !button.DeleteButton {
+			errMsg = "无操作权限"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	// 自定义分析,需要做这个指标关联的校验
+	if excelInfo.Source == utils.CUSTOM_ANALYSIS_TABLE {
+		list, tmpErr := excelModel.GetExcelEdbMappingByExcelInfoId(excelInfo.ExcelInfoId)
+		if tmpErr != nil {
+			errMsg = `获取关联的指标信息失败`
+			err = tmpErr
+			return
+		}
+
+		if len(list) > 0 {
+			errMsg = "已关联指标,不可删除!"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	// 标记删除
+	excelInfo.IsDelete = 1
+	excelInfo.ModifyTime = time.Now()
+	err = excelInfo.Update([]string{"IsDelete", "ModifyTime"})
+
+	return
+}
+
+// Copy 复制excel
+func Copy(oldExcelInfoId, excelClassifyId int, excelName string, sysUser *system.Admin) (excelInfo *excelModel.ExcelInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	excelName = utils.TrimLRStr(excelName)
+
+	excelClassify, err := excelModel.GetExcelClassifyById(excelClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		return
+	}
+	if excelClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	// 获取原ETA表格信息
+	oldExcelInfo, err := excelModel.GetExcelInfoById(oldExcelInfoId)
+	if err != nil {
+		errMsg = "获取ETA表格失败"
+		return
+	}
+
+	// 操作权限校验
+	{
+		button := GetExcelInfoOpButton(sysUser, oldExcelInfo.SysUserId, oldExcelInfo.Source)
+		if !button.CopyButton {
+			errMsg = "无操作权限"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	// 检验分类下是否存在该表格名称
+	{
+		var condition string
+		var pars []interface{}
+		condition += " AND excel_classify_id=? "
+		pars = append(pars, excelClassifyId)
+
+		condition += " AND excel_name=? "
+		pars = append(pars, excelName)
+
+		count, tmpErr := excelModel.GetExcelInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断表格名称是否存在失败"
+			err = tmpErr
+			return
+		}
+		if count > 0 {
+			errMsg = "表格名称已存在,请重新填写表格名称"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	// 表格信息
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	excelInfo = &excelModel.ExcelInfo{
+		//ExcelInfoId:     0,
+		ExcelName:       excelName,
+		Source:          oldExcelInfo.Source,
+		ExcelType:       oldExcelInfo.ExcelType,
+		UniqueCode:      utils.MD5(utils.EXCEL_DATA_PREFIX + "_" + timestamp),
+		ExcelClassifyId: excelClassifyId,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		Content:         oldExcelInfo.Content,
+		ExcelImage:      oldExcelInfo.ExcelImage,
+		FileUrl:         oldExcelInfo.FileUrl,
+		Sort:            0,
+		IsDelete:        0,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+
+	// 如果不是自定义分析,那么直接加主表就好了
+	if excelInfo.Source != utils.CUSTOM_ANALYSIS_TABLE {
+
+		// 获取excel与指标的关系表
+		list, tmpErr := excelModel.GetAllExcelEdbMappingByExcelInfoId(excelInfo.ExcelInfoId)
+		if tmpErr != nil {
+			errMsg = "获取失败"
+			err = tmpErr
+			return
+		}
+		for k, v := range list {
+			v.ExcelEdbMappingId = 0
+			v.ExcelInfoId = 0
+			list[k] = v
+		}
+
+		err = excelModel.AddExcelInfo(excelInfo, list)
+		if err != nil {
+			errMsg = "保存失败"
+		}
+
+		return
+	}
+
+	// 自定义分析,需要有额外信息
+	addSheetList := make([]excelModel.AddExcelSheetParams, 0)
+
+	// 获取所有的sheet页
+	oldSheetItemList, err := excelModel.GetAllSheetList(oldExcelInfo.ExcelInfoId)
+	if err != nil {
+		errMsg = `获取sheet页失败`
+		return
+	}
+
+	// 获取所有的sheet页的sheet数据
+	sheetCellDataMapList := make(map[int][]*excelModel.ExcelSheetData)
+	{
+		dataList, tmpErr := excelModel.GetAllSheetDataListByExcelInfoId(oldExcelInfo.ExcelInfoId)
+		if tmpErr != nil {
+			errMsg = `获取sheet页的单元格数据失败`
+			err = tmpErr
+			return
+		}
+
+		for _, cellData := range dataList {
+			sheetDataList, ok := sheetCellDataMapList[cellData.ExcelSheetId]
+			if !ok {
+				sheetDataList = make([]*excelModel.ExcelSheetData, 0)
+			}
+			sheetCellDataMapList[cellData.ExcelSheetId] = append(sheetDataList, cellData)
+		}
+	}
+
+	// sheet处理
+	for _, sheetInfo := range oldSheetItemList {
+		addSheetItem := excelModel.AddExcelSheetParams{
+			ExcelSheetId: 0,
+			ExcelInfoId:  0,
+			SheetName:    sheetInfo.SheetName,
+			Sort:         sheetInfo.Sort,
+			Config:       sheetInfo.Config,
+			CalcChain:    sheetInfo.CalcChain,
+		}
+
+		sheetDataList, ok := sheetCellDataMapList[sheetInfo.ExcelSheetId]
+		if ok {
+			for i, sheetData := range sheetDataList {
+				sheetData.ExcelDataId = 0
+				sheetData.ExcelSheetId = 0
+				sheetData.ExcelInfoId = 0
+				sheetDataList[i] = sheetData
+			}
+		}
+
+		addSheetItem.DataList = sheetDataList
+
+		addSheetList = append(addSheetList, addSheetItem)
+	}
+
+	// 添加表格
+	err = excelModel.AddExcelInfoAndSheet(excelInfo, addSheetList)
+
+	return
+}
+
+// UpdateExcelInfoFileUrl 更新excel表格的下载地址
+func UpdateExcelInfoFileUrl(excelInfo *excelModel.ExcelInfo) {
+	var err error
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg(fmt.Sprintf("更新excel表格的下载地址失败,表格id:%d;表格名称:%s; ERR:%s", excelInfo.ExcelInfoId, excelInfo.ExcelName, err), 3)
+			utils.FileLog.Info(fmt.Sprintf("更新excel表格的下载地址失败,表格id:%d;表格名称:%s; ERR:%s", excelInfo.ExcelInfoId, excelInfo.ExcelName, err), 3)
+		}
+	}()
+	fileName := excelInfo.ExcelName + "_" + excelInfo.UniqueCode + ".xlsx"
+
+	var downloadFilePath string // excel文件下载地址
+
+	switch excelInfo.Source {
+	case utils.EXCEL_DEFAULT: // 自定义表格
+		luckySheetData, tmpErr := excel.GetLuckySheetData(excelInfo.Content)
+		if tmpErr != nil {
+			err = tmpErr
+			fmt.Println("err:", err)
+			return
+		}
+		//_, err = luckySheetData.GetTableDataByLuckySheetDataStr()
+		downloadFilePath, err = luckySheetData.ToExcel()
+	case utils.CUSTOM_ANALYSIS_TABLE: // 自定义分析表格
+		downloadFilePath, err, _ = GenerateExcelCustomAnalysisExcel(excelInfo)
+	}
+
+	if err != nil {
+		fmt.Println("err:", err)
+		return
+	}
+	defer func() {
+		_ = os.Remove(downloadFilePath)
+	}()
+
+	var resourceUrl string
+	//上传到阿里云
+	if utils.ObjectStorageClient == "minio" {
+		resourceUrl, err = services.UploadImgToMinIo(fileName, downloadFilePath)
+	} else {
+		resourceUrl, err = services.UploadAliyunV2(fileName, downloadFilePath)
+	}
+	if err != nil {
+		return
+	}
+	excelInfo.FileUrl = resourceUrl
+	err = excelInfo.Update([]string{"FileUrl"})
+}

+ 331 - 0
services/data/jiayue_index.go

@@ -0,0 +1,331 @@
+package data
+
+import (
+	"encoding/json"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+)
+
+// 嘉悦桥接服务接口
+var (
+	BridgeApiJiaYueIndexUrl         = "/api/index_data/jiayue/index"          // 获取指标信息
+	BridgeApiJiaYueIndexDataUrl     = "/api/index_data/jiayue/index_data"     // 获取指标数据
+	BridgeApiJiaYuePageIndexUrl     = "/api/index_data/jiayue/page_index"     // 获取指标列表-分页
+	BridgeApiJiaYueFrequencyListUrl = "/api/index_data/jiayue/frequency_list" // 获取指标频度列表
+)
+
+type EdbBridgeJiaYue struct{}
+
+// GetIndex 获取指标信息
+func (brg *EdbBridgeJiaYue) GetIndex(req GetIndexFromBridgeReq) (item BridgeIndexItem, err error) {
+	var params data_manage.BridgeJiaYueIndexParams
+	params.IndexCode = req.IndexCode
+	params.SourceExtend = req.SourceExtend
+	params.IndexCodeRequired = req.IndexCodeRequired
+	indexData, e := GetJiaYueIndexFromBridge(params)
+	if e != nil {
+		err = fmt.Errorf("GetJiaYueIndexDataFromBridge err: %s", e.Error())
+		return
+	}
+	if indexData.Id <= 0 {
+		return
+	}
+	item.Id = indexData.Id
+	item.IndexCode = indexData.IndexCode
+	item.IndexName = indexData.IndexName
+	item.Unit = indexData.Unit
+	item.Frequency = brg.TransFrequency(indexData.Frequency)
+	return
+}
+
+// GetIndexAndData 获取指标和数据
+//func (brg *EdbBridgeJiaYue) GetIndexAndData(req GetIndexAndDataFromBridgeReq) (item BridgeIndexItem, err error) {
+//	var params data_manage.BridgeJiaYueIndexDataParams
+//	params.IndexCode = req.IndexCode
+//	params.SourceExtend = req.SourceExtend
+//	params.StartDate = req.StartDate
+//	params.EndDate = req.EndDate
+//	indexData, e := GetJiaYueIndexDataFromBridge(params)
+//	if e != nil {
+//		err = fmt.Errorf("GetJiaYueIndexDataFromBridge err: %s", e.Error())
+//		return
+//	}
+//	if indexData.Id <= 0 {
+//		return
+//	}
+//	item.Id = indexData.Id
+//	item.IndexCode = indexData.IndexCode
+//	item.IndexName = indexData.IndexName
+//	item.Unit = indexData.Unit
+//	item.Frequency = brg.TransFrequency(indexData.Frequency)
+//	var dataList []BridgeIndexDataItem
+//	for _, v := range indexData.IndexData {
+//		dataList = append(dataList, BridgeIndexDataItem{
+//			Val:        v.Val,
+//			DataTime:   v.DataTime,
+//			UpdateTime: v.UpdateTime,
+//		})
+//	}
+//	item.Data = dataList
+//	return
+//}
+
+// TransFrequency 嘉悦指标频度转换
+func (brg *EdbBridgeJiaYue) TransFrequency(origin string) string {
+	mapping := map[string]string{
+		"日":  "日度",
+		"周":  "周度",
+		"旬":  "旬度",
+		"半月": "旬度",
+		"月":  "月度",
+		"季":  "季度",
+		"半年": "半年度",
+		"年":  "年度",
+	}
+	return mapping[origin]
+}
+
+// GetJiaYueIndexFromBridge 从桥接服务获取指标信息
+func GetJiaYueIndexFromBridge(param data_manage.BridgeJiaYueIndexParams) (indexData data_manage.BridgeJiaYueIndexAndData, err error) {
+	defer func() {
+		if err != nil {
+			b, _ := json.Marshal(param)
+			tips := fmt.Sprintf("桥接服务-获取嘉悦指标信息, err: %s, params: %s", err.Error(), string(b))
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+
+	url := fmt.Sprint(utils.EtaBridgeUrl, BridgeApiJiaYueIndexUrl)
+	data, e := json.Marshal(param)
+	if e != nil {
+		err = fmt.Errorf("data json marshal err: %s", e.Error())
+		return
+	}
+	body := ioutil.NopCloser(strings.NewReader(string(data)))
+	client := &http.Client{}
+	req, e := http.NewRequest("POST", url, body)
+	if e != nil {
+		err = fmt.Errorf("http create request err: %s", e.Error())
+		return
+	}
+
+	checkToken := utils.MD5(utils.EtaBridgeAppNameEn + utils.EtaBridgeMd5Key)
+	contentType := "application/json;charset=utf-8"
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Authorization", checkToken)
+	resp, e := client.Do(req)
+	if e != nil {
+		err = fmt.Errorf("http client do err: %s", e.Error())
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	b, e := ioutil.ReadAll(resp.Body)
+	if e != nil {
+		err = fmt.Errorf("resp body read err: %s", e.Error())
+		return
+	}
+	if len(b) == 0 {
+		err = fmt.Errorf("resp body is empty")
+		return
+	}
+	// 生产环境解密
+	if utils.RunMode == "release" {
+		str := string(b)
+		str = strings.Trim(str, `"`)
+		b = utils.DesBase64Decrypt([]byte(str), utils.EtaBridgeDesKey)
+	}
+
+	result := new(data_manage.BridgeJiaYueResultIndexData)
+	if e = json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s", string(b))
+		return
+	}
+	indexData = result.Data
+	return
+}
+
+// GetJiaYueIndexDataFromBridge 从桥接服务获取指标数据
+//func GetJiaYueIndexDataFromBridge(param data_manage.BridgeJiaYueIndexDataParams) (indexData data_manage.BridgeJiaYueIndexAndData, err error) {
+//	defer func() {
+//		if err != nil {
+//			b, _ := json.Marshal(param)
+//			tips := fmt.Sprintf("桥接服务-获取嘉悦指标数据, err: %s, params: %s", err.Error(), string(b))
+//			go alarm_msg.SendAlarmMsg(tips, 3)
+//		}
+//	}()
+//
+//	url := fmt.Sprint(utils.EtaBridgeUrl, BridgeApiJiaYueIndexDataUrl)
+//	data, e := json.Marshal(param)
+//	if e != nil {
+//		err = fmt.Errorf("data json marshal err: %s", e.Error())
+//		return
+//	}
+//	body := ioutil.NopCloser(strings.NewReader(string(data)))
+//	client := &http.Client{}
+//	req, e := http.NewRequest("POST", url, body)
+//	if e != nil {
+//		err = fmt.Errorf("http create request err: %s", e.Error())
+//		return
+//	}
+//
+//	checkToken := utils.MD5(utils.EtaBridgeAppNameEn + utils.EtaBridgeMd5Key)
+//	contentType := "application/json;charset=utf-8"
+//	req.Header.Set("Content-Type", contentType)
+//	req.Header.Set("Authorization", checkToken)
+//	resp, e := client.Do(req)
+//	if e != nil {
+//		err = fmt.Errorf("http client do err: %s", e.Error())
+//		return
+//	}
+//	defer func() {
+//		_ = resp.Body.Close()
+//	}()
+//	b, e := ioutil.ReadAll(resp.Body)
+//	if e != nil {
+//		err = fmt.Errorf("resp body read err: %s", e.Error())
+//		return
+//	}
+//	if len(b) == 0 {
+//		err = fmt.Errorf("resp body is empty")
+//		return
+//	}
+//	// 生产环境解密
+//	if utils.RunMode == "release" {
+//		str := string(b)
+//		str = strings.Trim(str, `"`)
+//		b = utils.DesBase64Decrypt([]byte(str), utils.EtaBridgeDesKey)
+//	}
+//
+//	result := new(data_manage.BridgeJiaYueResultIndexData)
+//	if e = json.Unmarshal(b, &result); e != nil {
+//		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+//		return
+//	}
+//	if result.Code != 200 {
+//		err = fmt.Errorf("result: %s", string(b))
+//		return
+//	}
+//	indexData = result.Data
+//	return
+//}
+
+// GetJiaYueFrequencyListFromBridge 获取指标频度列表
+func GetJiaYueFrequencyListFromBridge() (frequencies []string, err error) {
+	url := fmt.Sprint(utils.EtaBridgeUrl, BridgeApiJiaYueFrequencyListUrl)
+	body := ioutil.NopCloser(strings.NewReader(""))
+	client := &http.Client{}
+	req, e := http.NewRequest("POST", url, body)
+	if e != nil {
+		err = fmt.Errorf("http create request err: %s", e.Error())
+		return
+	}
+
+	checkToken := utils.MD5(utils.EtaBridgeAppNameEn + utils.EtaBridgeMd5Key)
+	contentType := "application/json;charset=utf-8"
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Authorization", checkToken)
+	resp, e := client.Do(req)
+	if e != nil {
+		err = fmt.Errorf("http client do err: %s", e.Error())
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	b, e := ioutil.ReadAll(resp.Body)
+	if e != nil {
+		err = fmt.Errorf("resp body read err: %s", e.Error())
+		return
+	}
+	if len(b) == 0 {
+		err = fmt.Errorf("resp body is empty")
+		return
+	}
+	// 生产环境解密
+	if utils.RunMode == "release" {
+		str := string(b)
+		str = strings.Trim(str, `"`)
+		b = utils.DesBase64Decrypt([]byte(str), utils.EtaBridgeDesKey)
+	}
+
+	result := new(data_manage.BridgeJiaYueResultFrequencyList)
+	if e = json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s", string(b))
+		return
+	}
+	frequencies = result.Data
+	return
+}
+
+// GetJiaYueIndexPageListFromBridge 获取指标分页列表
+func GetJiaYueIndexPageListFromBridge(param data_manage.BridgeJiaYuePageIndexReq) (total int, items []data_manage.DictIndex, err error) {
+	url := fmt.Sprint(utils.EtaBridgeUrl, BridgeApiJiaYuePageIndexUrl)
+	data, e := json.Marshal(param)
+	if e != nil {
+		err = fmt.Errorf("data json marshal err: %s", e.Error())
+		return
+	}
+	body := ioutil.NopCloser(strings.NewReader(string(data)))
+	client := &http.Client{}
+	req, e := http.NewRequest("POST", url, body)
+	if e != nil {
+		err = fmt.Errorf("http create request err: %s", e.Error())
+		return
+	}
+
+	checkToken := utils.MD5(utils.EtaBridgeAppNameEn + utils.EtaBridgeMd5Key)
+	contentType := "application/json;charset=utf-8"
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Authorization", checkToken)
+	resp, e := client.Do(req)
+	if e != nil {
+		err = fmt.Errorf("http client do err: %s", e.Error())
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	b, e := ioutil.ReadAll(resp.Body)
+	if e != nil {
+		err = fmt.Errorf("resp body read err: %s", e.Error())
+		return
+	}
+	if len(b) == 0 {
+		err = fmt.Errorf("resp body is empty")
+		return
+	}
+	// 生产环境解密
+	if utils.RunMode == "release" {
+		str := string(b)
+		str = strings.Trim(str, `"`)
+		b = utils.DesBase64Decrypt([]byte(str), utils.EtaBridgeDesKey)
+	}
+
+	result := new(data_manage.BridgeJiaYueResultIndexPageList)
+	if e = json.Unmarshal(b, &result); e != nil {
+		err = fmt.Errorf("result unmarshal err: %s\nresult: %s", e.Error(), string(b))
+		return
+	}
+	if result.Code != 200 {
+		err = fmt.Errorf("result: %s", string(b))
+		return
+	}
+	total = result.Data.Total
+	items = result.Data.List
+	return
+}

+ 62 - 2
services/elastic.go

@@ -2,12 +2,13 @@ package services
 
 import (
 	"context"
-	"fmt"
-	"github.com/olivere/elastic/v7"
 	"eta/eta_api/models"
 	saModel "eta/eta_api/models/semantic_analysis"
+	"eta/eta_api/models/smart_report"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
+	"fmt"
+	"github.com/olivere/elastic/v7"
 	"strings"
 )
 
@@ -267,3 +268,62 @@ func EsAddOrEditSaDoc(indexName, docId string, item *saModel.ElasticSaDoc) (err
 	fmt.Println("AddData", resp.Status, resp.Result)
 	return
 }
+
+// EsAddOrEditSmartReport 新增编辑es智能研报
+func EsAddOrEditSmartReport(indexName, docId string, item *smart_report.ElasticSmartReport) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("EsAddOrEditSmartReport Err:", err.Error())
+		}
+	}()
+	client, err := NewClient()
+	if err != nil {
+		return
+	}
+	// docId为报告ID
+	searchById, err := client.Get().Index(indexName).Id(docId).Do(context.Background())
+	if err != nil && !strings.Contains(err.Error(), "404") {
+		fmt.Println("Get Err" + err.Error())
+		return
+	}
+	if searchById != nil && searchById.Found {
+		resp, err := client.Update().Index(indexName).Id(docId).Doc(map[string]interface{}{
+			"SmartReportId":      item.SmartReportId,
+			"Title":              item.Title,
+			"Abstract":           item.Abstract,
+			"BodyContent":        item.BodyContent,
+			"PublishTime":        item.PublishTime,
+			"PublishState":       item.PublishState,
+			"Author":             item.Author,
+			"ClassifyIdFirst":    item.ClassifyIdFirst,
+			"ClassifyNameFirst":  item.ClassifyNameFirst,
+			"ClassifyIdSecond":   item.ClassifyIdSecond,
+			"ClassifyNameSecond": item.ClassifyNameSecond,
+			"StageStr":           item.StageStr,
+			"Frequency":          item.Frequency,
+		}).Do(context.Background())
+		if err != nil {
+			return err
+		}
+		//fmt.Println(resp.Status, resp.Result)
+		if resp.Status == 0 {
+			fmt.Println("修改成功" + docId)
+			err = nil
+		} else {
+			fmt.Println("EditData", resp.Status, resp.Result)
+		}
+	} else {
+		resp, err := client.Index().Index(indexName).Id(docId).BodyJson(item).Do(context.Background())
+		if err != nil {
+			fmt.Println("新增失败:", err.Error())
+			return err
+		}
+		if resp.Status == 0 && resp.Result == "created" {
+			fmt.Println("新增成功" + docId)
+			return nil
+		} else {
+			fmt.Println("AddData", resp.Status, resp.Result)
+		}
+	}
+	return
+}

+ 379 - 0
services/excel/excel_to_lucky_sheet.go

@@ -0,0 +1,379 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"github.com/xuri/excelize/v2"
+	"sync"
+	"time"
+)
+
+// ConvToLuckySheet 普通的excel转luckySheet数据
+func ConvToLuckySheet(filePath string) (err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println(err)
+		}
+	}()
+	f, err := excelize.OpenFile(filePath)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	sheetDataList := make([]SimpleLuckySheetData, 0)
+	// 获取所有sheet
+	sheetList := f.GetSheetList()
+	fmt.Println("读取完成后", time.Now().Format(utils.FormatDateTime))
+
+	for sheetIndex, sheetName := range sheetList {
+		sheetData, tmpErr := getLuckySheetData(f, sheetIndex, sheetName)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		sheetDataList = append(sheetDataList, sheetData)
+	}
+
+	byteS, err := json.Marshal(sheetDataList)
+	if err != nil {
+		return
+	}
+	utils.FileLog.Info(string(byteS))
+
+	return
+}
+
+// SimpleLuckySheetData sheet表格数据
+type SimpleLuckySheetData struct {
+	Name  string `json:"name" description:"工作表名称"`
+	Index int    `json:"index" description:"工作表索引"`
+	//Row      int                        `json:"row" description:"行数"`
+	//Column   int                        `json:"column" description:"列数"`
+	CellData  []SimpleLuckySheetCellData `json:"celldata" description:"单元格数据"`
+	Config    SimpleLuckySheetDataConfig `json:"config" description:""`
+	CalcChain []CalcChain                `json:"calcChain" description:"公式链"`
+	//Status            int64   `json:"status" description:"激活状态"`
+}
+
+type CalcChain struct {
+	Col   int64         `json:"c"`     //列数
+	Row   int64         `json:"r"`     //行数
+	Index int           `json:"index"` //工作表id
+	Func  []interface{} `json:"func"`  //公式信息,包含公式计算结果和公式字符串
+	Color string        `json:"color"` //"w":采用深度优先算法 "b":普通计算
+	//Parent  interface{}   `json:"parent"`
+	//Chidren struct {
+	//} `json:"chidren"`
+	Times int `json:"times"`
+}
+
+// SimpleLuckySheetDataConfig sheet表单的配置
+type SimpleLuckySheetDataConfig struct {
+	BorderInfo []LuckySheetDataConfigBorderInfo `json:"borderInfo" description:"边框"`
+	Colhidden  map[string]int64                 `json:"colhidden" description:"隐藏列,示例值:\"colhidden\":{\"30\":0,\"31\":0}"`
+	//CustomHeight struct {
+	//	Zero int64 `json:"0"`
+	//} `json:"customHeight" description:""`
+	//CustomWidth struct {
+	//	Two int64 `json:"2" description:""`
+	//} `json:"customWidth" description:""`
+	Merge map[string]LuckySheetDataConfigMerge `json:"merge" description:"合并单元格"`
+	//Rowlen map[string]float64                   `json:"rowlen" description:"每个单元格的行高"`
+	Columnlen map[string]float64 `json:"columnlen" description:"每个单元格的列宽"`
+}
+
+// SimpleLuckySheetCellData 单个单元格数据
+type SimpleLuckySheetCellData struct {
+	Col int64 `json:"c" description:"列"`
+	Row int64 `json:"r" description:"行"`
+	//Value SimpleLuckySheetDataValue `json:"v" description:"单元格内值的数据"`
+	Value interface{} `json:"v" description:"单元格内值的数据"`
+}
+
+// SimpleLuckySheetDataValue 单元格内值的数据
+type SimpleLuckySheetDataValue struct {
+	CellType LuckySheetDataCellType `json:"ct" description:"单元格值格式:文本、时间等	"`
+	Value    interface{}            `json:"v" description:"原始值"`
+	//Monitor  string                 `json:"m" description:"显示值"`
+	//Fontsize       int                    `description:"字体大小,14"`
+	//TextBeak int         `description:"文本换行,	0 截断、1溢出、2 自动换行"`
+	//Tb       interface{} `json:"tb" description:"文本换行,	0 截断、1溢出、2 自动换行"`
+	//Ps        LuckySheetDataCellComment `json:"ps" description:"批注"`
+	Function string `json:"f" description:"公式"`
+	//MergeCell LuckySheetDataConfigMerge `json:"mc" description:"合并单元格信息"`
+}
+
+func getLuckySheetData(f *excelize.File, sheetIndex int, sheetName string) (sheetData SimpleLuckySheetData, err error) {
+	cellData := make([]SimpleLuckySheetCellData, 0)         // excel数据
+	mergeData := make(map[string]LuckySheetDataConfigMerge) //合并单元格数据
+	calcChainList := make([]CalcChain, 0)                   //公式链信息
+
+	sheetData = SimpleLuckySheetData{
+		Name:  sheetName,
+		Index: sheetIndex,
+		//Row:      0,
+		//Column:   0,
+		CellData: cellData,
+		Config:   SimpleLuckySheetDataConfig{},
+	}
+	fmt.Println("开始读取sheet数据:", time.Now().Format(utils.FormatDateTime))
+	rows, tmpErr := f.GetRows(sheetName)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	fmt.Println("读取完sheet数据:", time.Now().Format(utils.FormatDateTime))
+	lenRow := len(rows)
+	fmt.Println("总共:", lenRow, "条数据")
+	if lenRow <= 0 {
+		return
+	}
+
+	//sheetData.Row = len(rows)
+	//sheetData.Column = len(Column)
+
+	// 最大单元格数
+	maxColumnIndex := 0
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+	// 协程处理合并单元格
+	go func() {
+		defer func() {
+			wg.Done()
+		}()
+		mergeCellList, err := f.GetMergeCells(sheetName)
+		if err != nil {
+			return
+		}
+		for _, v := range mergeCellList {
+			// 左上角单元格位置
+			cStartIndex, rStartIndex, tmpErr := excelize.CellNameToCoordinates(v.GetStartAxis())
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			// 右下角单元格位置
+			cEndIndex, rEndIndex, tmpErr := excelize.CellNameToCoordinates(v.GetEndAxis())
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			//fmt.Println(v.GetEndAxis())
+			tmpLuckySheetDataConfigMerge := LuckySheetDataConfigMerge{
+				Row:    rStartIndex - 1,
+				Column: cStartIndex - 1,
+				Rs:     rEndIndex - rStartIndex + 1,
+				Cs:     cEndIndex - cStartIndex + 1,
+			}
+			mergeData[fmt.Sprint(rStartIndex-1, "_", cStartIndex-1)] = tmpLuckySheetDataConfigMerge
+		}
+		sheetData.Config.Merge = mergeData
+	}()
+
+	colWidthMap := make(map[string]float64)
+
+	// 每次分割就是5000条
+	splitNum := 500
+	splitLen := lenRow / splitNum
+	residue := lenRow % splitNum
+	if residue > 0 {
+		splitLen += 1
+	}
+
+	for i := 0; i < splitLen; i++ {
+		wg.Add(1)
+
+		startRow := i * splitNum
+		endRow := (i + 1) * splitNum
+		if i == splitLen-1 && residue > 0 {
+			endRow = lenRow
+		}
+
+		go func(currStartRow, currEndRow int) {
+			defer func() {
+				wg.Done()
+			}()
+
+			for rIndex := currStartRow; rIndex < currEndRow; rIndex++ {
+				row := rows[rIndex]
+				for cIndex, colCell := range row {
+					if rIndex == 0 {
+						//colName, tmpErr := excelize.ColumnNumberToName(cIndex + 1)
+						//if tmpErr != nil {
+						//	err = tmpErr
+						//	return
+						//}
+						//colWidth, tmpErr := f.GetColWidth(sheetName, colName)
+						//if tmpErr != nil {
+						//	err = tmpErr
+						//	return
+						//}
+						//colWidthMap[fmt.Sprint(cIndex)] = emuToPx(colWidth)
+					}
+					if maxColumnIndex < cIndex {
+						maxColumnIndex = cIndex
+					}
+					cellName, tmpErr := excelize.CoordinatesToCellName(cIndex+1, rIndex+1)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//cellType, tmpErr := f.GetCellType(sheetName, cellName)
+					//if tmpErr != nil {
+					//	err = tmpErr
+					//	return
+					//}
+					cellFormula, tmpErr := f.GetCellFormula(sheetName, cellName)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+
+					//fmt.Println(cellName, ": ", "类型:", cellType, ",公式:", cellFormula, ",值", colCell, "\t")
+
+					var colCellIntfac interface{}
+					cellTypeT := "g"
+					//colCell = utils.Tof
+					tmpDec, tmpErr := decimal.NewFromString(colCell)
+					if tmpErr != nil {
+						colCellIntfac = colCell
+					} else {
+						colCellIntfac, _ = tmpDec.Float64()
+						cellTypeT = "n"
+					}
+
+					if cellFormula != `` {
+						cellFormula = `=` + cellFormula
+
+						calcChainList = append(calcChainList, CalcChain{
+							Col:   int64(cIndex),
+							Row:   int64(rIndex),
+							Index: sheetIndex,
+							Func:  []interface{}{true, colCell, cellFormula},
+							Color: "w",
+							Times: 0,
+						})
+					}
+					//CellType LuckySheetDataCellType `json:"ct" description:"单元格值格式:文本、时间等	"`
+					//Value    interface{}            `json:"v" description:"原始值"`
+					////Monitor  string                 `json:"m" description:"显示值"`
+					////Fontsize       int                    `description:"字体大小,14"`
+					////TextBeak int         `description:"文本换行,	0 截断、1溢出、2 自动换行"`
+					////Tb       interface{} `json:"tb" description:"文本换行,	0 截断、1溢出、2 自动换行"`
+					////Ps        LuckySheetDataCellComment `json:"ps" description:"批注"`
+					//Function string `json:"f" description:"公式"`
+
+					valueMap := make(map[string]interface{})
+					valueMap["ct"] = LuckySheetDataCellType{
+						Fa: "General",
+						T:  cellTypeT,
+					}
+					valueMap["v"] = colCellIntfac
+					if cellFormula != `` {
+						valueMap["f"] = cellFormula
+					}
+					cellData = append(cellData, SimpleLuckySheetCellData{
+						Col:   int64(cIndex),
+						Row:   int64(rIndex),
+						Value: valueMap,
+					})
+				}
+			}
+		}(startRow, endRow)
+	}
+	wg.Wait()
+
+	fmt.Println("解析完sheet数据:", time.Now().Format(utils.FormatDateTime))
+	//for rIndex, row := range rows {
+	//	for cIndex, colCell := range row {
+	//		if rIndex == 0 {
+	//			//colName, tmpErr := excelize.ColumnNumberToName(cIndex + 1)
+	//			//if tmpErr != nil {
+	//			//	err = tmpErr
+	//			//	return
+	//			//}
+	//			//colWidth, tmpErr := f.GetColWidth(sheetName, colName)
+	//			//if tmpErr != nil {
+	//			//	err = tmpErr
+	//			//	return
+	//			//}
+	//			//colWidthMap[fmt.Sprint(cIndex)] = emuToPx(colWidth)
+	//		}
+	//		if maxColumnIndex < cIndex {
+	//			maxColumnIndex = cIndex
+	//		}
+	//		cellName, tmpErr := excelize.CoordinatesToCellName(cIndex+1, rIndex+1)
+	//		if tmpErr != nil {
+	//			err = tmpErr
+	//			return
+	//		}
+	//		//cellType, tmpErr := f.GetCellType(sheetName, cellName)
+	//		//if tmpErr != nil {
+	//		//	err = tmpErr
+	//		//	return
+	//		//}
+	//		cellFormula, tmpErr := f.GetCellFormula(sheetName, cellName)
+	//		if tmpErr != nil {
+	//			err = tmpErr
+	//			return
+	//		}
+	//
+	//		//fmt.Println(cellName, ": ", "类型:", cellType, ",公式:", cellFormula, ",值", colCell, "\t")
+	//
+	//		var colCellIntfac interface{}
+	//		cellTypeT := "g"
+	//		//colCell = utils.Tof
+	//		tmpDec, tmpErr := decimal.NewFromString(colCell)
+	//		if tmpErr != nil {
+	//			colCellIntfac = colCell
+	//		} else {
+	//			colCellIntfac, _ = tmpDec.Float64()
+	//			cellTypeT = "n"
+	//		}
+	//
+	//		if cellFormula != `` {
+	//			cellFormula = `=` + cellFormula
+	//
+	//			calcChainList = append(calcChainList, CalcChain{
+	//				Col:   int64(cIndex),
+	//				Row:   int64(rIndex),
+	//				Index: sheetIndex,
+	//				Func:  []interface{}{true, colCell, cellFormula},
+	//				Color: "w",
+	//				Times: 0,
+	//			})
+	//		}
+	//
+	//		cellData = append(cellData, SimpleLuckySheetCellData{
+	//			Col: int64(cIndex),
+	//			Row: int64(rIndex),
+	//			Value: SimpleLuckySheetDataValue{
+	//				CellType: LuckySheetDataCellType{
+	//					Fa: "General",
+	//					T:  cellTypeT,
+	//				},
+	//				Value:    colCellIntfac,
+	//				Monitor:  colCell,
+	//				Function: cellFormula,
+	//				//MergeCell: LuckySheetDataConfigMerge{},
+	//			},
+	//		})
+	//	}
+	//}
+
+	sheetData.Config.Columnlen = colWidthMap
+
+	sheetData.CellData = cellData
+	sheetData.CalcChain = calcChainList
+	//sheetData.Column = maxColumnIndex + 1
+
+	return
+}
+
+// emuToPx 计量单位emu转px
+func emuToPx(num float64) float64 {
+	return num * 15
+}

+ 71 - 27
services/excel/lucky_sheet.go

@@ -3,7 +3,7 @@ package excel
 import (
 	"encoding/json"
 	"errors"
-	"eta/eta_api/models/data_manage/request"
+	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/tealeg/xlsx"
@@ -107,17 +107,22 @@ type LuckySheetDataBak struct {
 
 // LuckySheetData sheet表格数据
 type LuckySheetData struct {
+	Name     string               `json:"name" description:"工作表名称"`
+	Index    interface{}          `json:"index" description:"工作表索引"`
+	Row      int                  `json:"row" description:"行数"`
+	Column   int                  `json:"column" description:"列数"`
 	CellData []LuckySheetCellData `json:"celldata" description:"单元格数据"`
 	ChWidth  int64                `json:"ch_width" description:"工作表区域的宽度"`
 	Config   LuckySheetDataConfig `json:"config" description:""`
 	//Index             int                  `json:"index" description:"工作表索引"`
-	RhHeight          float64 `json:"rh_height" description:"工作表区域的高度"`
-	ScrollLeft        float64 `json:"scrollLeft" description:"左右滚动条位置"`
-	ScrollTop         float64 `json:"scrollTop" description:"上下滚动条位置"`
-	Status            int64   `json:"status" description:"激活状态"`
-	VisibleDataColumn []int64 `json:"visibledatacolumn" description:"所有列的位置信息,递增的列位置数据,初始化无需设置"`
-	VisibleDataRow    []int64 `json:"visibledatarow" description:"所有行的位置信息,递增的行位置数据,初始化无需设置"`
-	ZoomRatio         float64 `json:"zoomRatio" description:"sheet缩放比例"`
+	RhHeight          float64       `json:"rh_height" description:"工作表区域的高度"`
+	ScrollLeft        float64       `json:"scrollLeft" description:"左右滚动条位置"`
+	ScrollTop         float64       `json:"scrollTop" description:"上下滚动条位置"`
+	CalcChain         []interface{} `json:"calcChain" description:"公式链"`
+	Status            interface{}   `json:"status" description:"激活状态"`
+	VisibleDataColumn []int64       `json:"visibledatacolumn" description:"所有列的位置信息,递增的列位置数据,初始化无需设置"`
+	VisibleDataRow    []int64       `json:"visibledatarow" description:"所有行的位置信息,递增的行位置数据,初始化无需设置"`
+	ZoomRatio         float64       `json:"zoomRatio" description:"sheet缩放比例"`
 }
 
 // LuckySheetDataConfig sheet表单的配置
@@ -331,6 +336,27 @@ func (tableData TableData) ToExcel() (downloadFilePath string, err error) {
 	if err != nil {
 		return
 	}
+
+	// 将单个sheet的数据写入到excel
+	err = tableData.WriteExcelSheetData(xlsxFile, "sheet1")
+	if err != nil {
+		return
+	}
+
+	//return
+	err = xlsxFile.Save(downloadFilePath)
+	if err != nil {
+		return
+	}
+	//randStr := time.Now().Format(utils.FormatDateTimeUnSpace)
+	//downloadFileName := "即将到期客户数据_" + randStr + ".xlsx"
+	//this.Ctx.Output.Download(downLoadnFilePath, downloadFileName)
+
+	return
+}
+
+// WriteExcelSheetData 通过 TableData生成excel表格数据
+func (tableData TableData) WriteExcelSheetData(xlsxFile *xlsx.File, sheetName string) (err error) {
 	style := xlsx.NewStyle()
 	alignment := xlsx.Alignment{
 		Horizontal: "center",
@@ -341,7 +367,7 @@ func (tableData TableData) ToExcel() (downloadFilePath string, err error) {
 	style.Alignment = alignment
 	style.ApplyAlignment = true
 
-	sheet, err := xlsxFile.AddSheet("sheet1")
+	sheet, err := xlsxFile.AddSheet(sheetName)
 	if err != nil {
 		return
 	}
@@ -419,7 +445,24 @@ func (tableData TableData) ToExcel() (downloadFilePath string, err error) {
 					}
 				}
 			}
-			tmpRow.SetString(valueStr)
+			//tmpRow.SetString(valueStr)
+			switch cellInfo.CellType.Fa {
+			case "General":
+				if cellInfo.CellType.S != nil {
+					tmpRow.SetString(valueStr)
+				} else {
+					tmpRow.SetValue(cellInfo.Value)
+				}
+			case "@":
+				tmpRow.SetString(valueStr)
+			default:
+				tmpRow.SetString(valueStr)
+			}
+			if cellInfo.Function != `` {
+				//xlsxFile.
+				//xlsxFile.SetCellFormula
+				tmpRow.SetFormula(cellInfo.Function)
+			}
 			//if cellInfo.Function != `` {
 			//	tmpRow.SetFormula(cellInfo.Function)
 			//}
@@ -433,19 +476,12 @@ func (tableData TableData) ToExcel() (downloadFilePath string, err error) {
 			}
 		}
 	}
-	//return
-	err = xlsxFile.Save(downloadFilePath)
-	if err != nil {
-		return
-	}
-	//randStr := time.Now().Format(utils.FormatDateTimeUnSpace)
-	//downloadFileName := "即将到期客户数据_" + randStr + ".xlsx"
-	//this.Ctx.Output.Download(downLoadnFilePath, downloadFileName)
+
 	return
 }
 
 // GetTableDataByLuckySheetDataStr 通过LuckySheet的string数据获取表格数据
-func (item *LuckySheetData) GetTableDataByLuckySheetDataStr() (selfTableData TableData, err error) {
+func (item *LuckySheetData) GetTableDataByLuckySheetDataStr(isRemoveBlankCell bool) (selfTableData TableData, err error) {
 	luckySheetCellDataList := item.CellData
 	// 表格数据
 	tableDataMap := make(map[int64]map[int64]LuckySheetDataValue)
@@ -526,8 +562,13 @@ func (item *LuckySheetData) GetTableDataByLuckySheetDataStr() (selfTableData Tab
 		tableDataList = append(tableDataList, tmpTableColDataList)
 	}
 
+	tableDataMergeList := make([]TableDataMerge, 0)
+	tableRemoveNum := TableRemoveNum{}
+
 	// 数据处理,移除上下左右空行空列
-	tableDataList, tableRemoveNum, rowHeightList, rowWidthList, tableDataMergeList := handleTableDataList(tableDataList, item.Config.Merge, rowHeightList, rowWidthList)
+	if isRemoveBlankCell {
+		tableDataList, tableRemoveNum, rowHeightList, rowWidthList, tableDataMergeList = handleTableDataList(tableDataList, item.Config.Merge, rowHeightList, rowWidthList)
+	}
 
 	// 表格数据
 	{
@@ -725,7 +766,7 @@ func handleTableDataList(tableDataList [][]LuckySheetDataValue, luckySheetDataCo
 
 // ToExcel 通过 luckySheetData生成excel表格
 func (item *LuckySheetData) ToExcel() (downloadFilePath string, err error) {
-	tableData, err := item.GetTableDataByLuckySheetDataStr()
+	tableData, err := item.GetTableDataByLuckySheetDataStr(true)
 	if err != nil {
 		return
 	}
@@ -862,10 +903,10 @@ var LuckyFontFamilyMap = map[int]string{
 	2:  "Tahoma",
 	3:  "Verdana",
 	4:  "微软雅黑",
-	5:  "宋体",  //宋体(Song)、
-	6:  "黑体",  // 黑体(ST Heiti)
-	7:  "楷体",  //楷体(ST Kaiti),
-	8:  "仿宋",  //仿宋(ST FangSong),
+	5:  "宋体",   //宋体(Song)、
+	6:  "黑体",   // 黑体(ST Heiti)
+	7:  "楷体",   //楷体(ST Kaiti),
+	8:  "仿宋",   //仿宋(ST FangSong),
 	9:  "新宋体", //新宋体(ST Song),
 	10: "华文新魏",
 	11: "华文行楷",
@@ -1165,7 +1206,7 @@ func (item *LuckySheetData) GetTableDataByLuckySheetDataStrBak() (selfTableData
 }
 
 func (item *LuckySheetData) ToExcel2() (downloadFilePath string, err error) {
-	tableData, err := item.GetTableDataByLuckySheetDataStr()
+	tableData, err := item.GetTableDataByLuckySheetDataStr(true)
 
 	downloadFilePath, err = getDownloadPath()
 	if err != nil {
@@ -1175,7 +1216,10 @@ func (item *LuckySheetData) ToExcel2() (downloadFilePath string, err error) {
 	f := excelize.NewFile()
 	// Create a new sheet.
 	sheetName := `Sheet1`
-	sheetIndex := f.NewSheet(sheetName)
+	sheetIndex, err := f.NewSheet(sheetName)
+	if err != nil {
+		return
+	}
 
 	//设置列宽度
 	for k, v := range tableData.RowWidthList {

+ 49 - 0
services/excel/lucky_sheet_excel.go

@@ -0,0 +1,49 @@
+package excel
+
+import "github.com/tealeg/xlsx"
+
+type LuckySheet struct {
+	SheetList []LuckySheetData `description:"sheet数据"`
+}
+
+// GetExcelData 通过 luckySheetData获取excel表格数据
+func (item *LuckySheet) GetExcelData(isRemoveBlankCell bool) (xlsxFile *xlsx.File, err error) {
+	xlsxFile = xlsx.NewFile()
+	if err != nil {
+		return
+	}
+
+	for _, sheet := range item.SheetList {
+		tableData, tmpErr := sheet.GetTableDataByLuckySheetDataStr(isRemoveBlankCell)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		err = tableData.WriteExcelSheetData(xlsxFile, sheet.Name)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// ToExcel 通过 luckySheetData生成excel表格文件
+func (item *LuckySheet) ToExcel(isRemoveBlankCell bool) (downloadFilePath string, err error) {
+	// 获取生成的excel路径
+	downloadFilePath, err = getDownloadPath()
+	if err != nil {
+		return
+	}
+
+	// 获取excel表格数据
+	xlsxFile, err := item.GetExcelData(isRemoveBlankCell)
+	if err != nil {
+		return
+	}
+
+	// 文件保存
+	err = xlsxFile.Save(downloadFilePath)
+
+	return
+}

+ 52 - 0
services/excel/xml.go

@@ -0,0 +1,52 @@
+package excel
+
+import (
+	"errors"
+	"github.com/beevik/etree"
+)
+
+// GetDefaultSheetIndex 获取数据源工作表索引
+func GetDefaultSheetIndex(workbookPath string, defaultSheetName string) (defaultSheetIndex int, err error) {
+	doc := etree.NewDocument()
+	if err = doc.ReadFromFile(workbookPath); err != nil {
+		return
+	}
+	flag := false
+	workbook := doc.SelectElement("workbook")
+	sheets := workbook.SelectElement("sheets")
+	for index, sheet := range sheets.SelectElements("sheet") {
+		for _, attr := range sheet.Attr {
+			if attr.Key == "name" && attr.Value == defaultSheetName {
+				defaultSheetIndex = index
+				flag = true
+			}
+		}
+	}
+	if flag == false {
+		err = errors.New(defaultSheetName + "工作表未找到")
+	}
+	return
+}
+
+// GetDefaultSheetIndex 获取数据源工作表索引
+func GetDefaultSheetData(sheetPath string, defaultSheetName string) (defaultSheetIndex int, err error) {
+	doc := etree.NewDocument()
+	if err = doc.ReadFromFile(sheetPath); err != nil {
+		return
+	}
+	flag := false
+	workbook := doc.SelectElement("worksheet")
+	sheets := workbook.SelectElement("sheets")
+	for index, sheet := range sheets.SelectElements("sheet") {
+		for _, attr := range sheet.Attr {
+			if attr.Key == "name" && attr.Value == defaultSheetName {
+				defaultSheetIndex = index
+				flag = true
+			}
+		}
+	}
+	if flag == false {
+		err = errors.New(defaultSheetName + "工作表未找到")
+	}
+	return
+}

+ 63 - 0
services/excel_info.go

@@ -0,0 +1,63 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// UpdateExcelEditMark 更新表格当前更新状态
+// status 枚举值 1:编辑中,0:完成编辑, 2:只做查询
+func UpdateExcelEditMark(excelInfoId, nowUserId, status int, nowUserName string) (ret models.MarkReportResp, err error) {
+	//更新标记key
+	key := fmt.Sprint(`crm:excel:edit:`, excelInfoId)
+	opUserId, e := utils.Rc.RedisInt(key)
+	var opUser models.MarkReportItem
+	if e != nil {
+		opUserInfoStr, tErr := utils.Rc.RedisString(key)
+		if tErr == nil {
+			tErr = json.Unmarshal([]byte(opUserInfoStr), &opUser)
+			if tErr == nil {
+				opUserId = opUser.AdminId
+			}
+		}
+	}
+	if opUserId > 0 && opUserId != nowUserId {
+		editor := opUser.Editor
+		if editor == "" {
+			//查询账号的用户姓名
+			otherInfo, e := system.GetSysAdminById(opUserId)
+			if e != nil {
+				err = fmt.Errorf("查询其他编辑者信息失败")
+				return
+			}
+			editor = otherInfo.RealName
+		}
+
+		ret.Status = 1
+		ret.Msg = fmt.Sprintf("当前%s正在编辑中", editor)
+		ret.Editor = editor
+		return
+	}
+	if status == 1 {
+		nowUser := &models.MarkReportItem{AdminId: nowUserId, Editor: nowUserName}
+		bt, e := json.Marshal(nowUser)
+		if e != nil {
+			err = fmt.Errorf("格式化编辑者信息失败")
+			return
+		}
+		if opUserId > 0 {
+			utils.Rc.Do("SETEX", key, int64(300), string(bt)) //3分钟缓存
+		} else {
+			utils.Rc.SetNX(key, string(bt), time.Second*60*5) //3分钟缓存
+		}
+	} else if status == 0 {
+		//清除编辑缓存
+		_ = utils.Rc.Delete(key)
+	}
+	return
+}
+

+ 8 - 6
services/minio.go

@@ -117,7 +117,7 @@ func UploadMinIo() {
 	log.Printf("Successfully uploaded %s of size %d\n", objectName, info.Size)
 }
 
-//UploadImgToMinIo 图片上传
+// UploadImgToMinIo 图片上传
 func UploadImgToMinIo(fileName, filePath string) (string, error) {
 	if utils.MinIoAccessKeyId == `` || utils.MinIoAccessKeySecret == `` {
 		return "0", errors.New("MinIo信息未配置")
@@ -137,6 +137,7 @@ func UploadImgToMinIo(fileName, filePath string) (string, error) {
 	})
 	if err != nil {
 		log.Fatalln(err)
+		return "1", err
 	}
 	bucketName := utils.MinIoBucketname
 	// Check to see if we already own this bucket (which happens if you run this twice)
@@ -145,14 +146,16 @@ func UploadImgToMinIo(fileName, filePath string) (string, error) {
 		log.Printf("We already own %s\n", bucketName)
 	} else {
 		log.Fatalln(err)
+		return "2", err
 	}
-	path := utils.MinIoUpload_Audio_Dir + time.Now().Format("200601/20060102/")
+	path := utils.MinIoUploadDir + time.Now().Format("200601/20060102/")
 	path += fileName
 	// Upload the zip file with FPutObject
 	//contentType := "application/xlsx"
 	_, err = minioClient.FPutObject(ctx, bucketName, path, filePath, minio.PutObjectOptions{})
 	if err != nil {
 		log.Fatalln(err)
+		return "3", err
 	}
 
 	path = utils.MinIoImghost + path
@@ -165,7 +168,6 @@ func UploadAudioToMinIo(fileName, filePath string) (string, error) {
 		return "0", errors.New("MinIo信息未配置")
 	}
 
-
 	ctx := context.Background()
 	endpoint := utils.MinIoEndpoint
 	accessKeyID := utils.MinIoAccessKeyId
@@ -197,7 +199,7 @@ func UploadAudioToMinIo(fileName, filePath string) (string, error) {
 
 	// Upload the zip file with FPutObject
 	//contentType := "application/xlsx"
-	_, err = minioClient.FPutObject(ctx, bucketName, fileName, filePath, minio.PutObjectOptions{})
+	_, err = minioClient.FPutObject(ctx, bucketName, path, filePath, minio.PutObjectOptions{})
 	if err != nil {
 		log.Fatalln(err)
 		return "3", err
@@ -318,7 +320,7 @@ func UploadMinIoToDir(filename, filePath, uploadDir, fileDir string) (string, er
 	})
 	if err != nil {
 		log.Fatalln(err)
-		return "1",err
+		return "1", err
 	}
 	bucketName := utils.MinIoBucketname
 	// Check to see if we already own this bucket (which happens if you run this twice)
@@ -327,7 +329,7 @@ func UploadMinIoToDir(filename, filePath, uploadDir, fileDir string) (string, er
 		log.Printf("We already own %s\n", bucketName)
 	} else {
 		log.Fatalln(err)
-		return "2",err
+		return "2", err
 	}
 	if uploadDir == "" {
 		uploadDir = utils.MinIoUploadDir

+ 516 - 299
services/sandbox/sandbox.go

@@ -2,7 +2,6 @@ package sandbox
 
 import (
 	"encoding/json"
-	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/sandbox"
@@ -15,215 +14,215 @@ import (
 )
 
 // AddSandbox 新增沙盘
-func AddSandbox(req request.AddAndEditSandbox, opUserId int, opUserName string, ignoreVariety bool) (resp *sandbox.SandboxSaveResp, err error) {
-	resp = new(sandbox.SandboxSaveResp)
-	// 获取产品权限详情
-	var permissionName string
-	if !ignoreVariety {
-		chartPermissionInfo, e := company.GetChartPermissionListById(req.ChartPermissionId)
-		if e != nil {
-			err = e
-			return
-		}
-		permissionName = chartPermissionInfo.PermissionName
-	}
-	//沙盘主表信息
-	sandboxInfo := &sandbox.Sandbox{
-		Name:                utils.TrimStr(req.Name),
-		ChartPermissionId:   req.ChartPermissionId,
-		ChartPermissionName: permissionName,
-		CurrVersion:         1,
-		Code:                GenerateCode(),
-		Content:             req.Content,
-		PicUrl:              utils.TrimStr(req.PicUrl),
-		OpUserId:            opUserId,
-		OpUserName:          opUserName,
-		IsDelete:            0,
-		ModifyTime:          time.Now(),
-		CreateTime:          time.Now(),
-	}
-	//沙盘版本表信息
-	sandboxVersionInfo := &sandbox.SandboxVersion{
-		Name:                sandboxInfo.Name,
-		ChartPermissionId:   sandboxInfo.ChartPermissionId,
-		ChartPermissionName: sandboxInfo.ChartPermissionName,
-		CurrVersion:         sandboxInfo.CurrVersion,
-		Content:             sandboxInfo.Content,
-		PicUrl:              sandboxInfo.PicUrl,
-		OpUserId:            sandboxInfo.OpUserId,
-		OpUserName:          sandboxInfo.OpUserName,
-		VersionCode:         GenerateVersionCode(sandboxInfo.SandboxId, sandboxInfo.CurrVersion),
-		IsDelete:            sandboxInfo.IsDelete,
-		CreateTime:          sandboxInfo.CreateTime,
-	}
-	//沙盘草稿表信息
-	sandboxDraftInfo := &sandbox.SandboxDraft{
-		Name:                sandboxInfo.Name,
-		ChartPermissionId:   sandboxInfo.ChartPermissionId,
-		ChartPermissionName: sandboxInfo.ChartPermissionName,
-		CurrVersion:         sandboxInfo.CurrVersion,
-		Content:             sandboxInfo.Content,
-		OpUserId:            sandboxInfo.OpUserId,
-		OpUserName:          sandboxInfo.OpUserName,
-		CreateTime:          sandboxInfo.CreateTime,
-	}
-
-	//新增沙盘
-	err = sandbox.AddNewSandbox(sandboxInfo, sandboxVersionInfo, sandboxDraftInfo)
-	if err != nil {
-		return
-	}
-	resp.Sandbox = sandboxInfo
-	resp.VersionCode = sandboxVersionInfo.VersionCode
-	return
-}
+//func AddSandbox(req request.AddAndEditSandbox, opUserId int, opUserName string, ignoreVariety bool) (resp *sandbox.SandboxSaveResp, err error) {
+//	resp = new(sandbox.SandboxSaveResp)
+//	// 获取产品权限详情
+//	var permissionName string
+//	if !ignoreVariety {
+//		chartPermissionInfo, e := company.GetChartPermissionListById(req.ChartPermissionId)
+//		if e != nil {
+//			err = e
+//			return
+//		}
+//		permissionName = chartPermissionInfo.PermissionName
+//	}
+//	//沙盘主表信息
+//	sandboxInfo := &sandbox.Sandbox{
+//		Name:                utils.TrimStr(req.Name),
+//		ChartPermissionId:   req.ChartPermissionId,
+//		ChartPermissionName: permissionName,
+//		CurrVersion:         1,
+//		Code:                GenerateCode(),
+//		Content:             req.Content,
+//		PicUrl:              utils.TrimStr(req.PicUrl),
+//		OpUserId:            opUserId,
+//		OpUserName:          opUserName,
+//		IsDelete:            0,
+//		ModifyTime:          time.Now(),
+//		CreateTime:          time.Now(),
+//	}
+//	//沙盘版本表信息
+//	sandboxVersionInfo := &sandbox.SandboxVersion{
+//		Name:                sandboxInfo.Name,
+//		ChartPermissionId:   sandboxInfo.ChartPermissionId,
+//		ChartPermissionName: sandboxInfo.ChartPermissionName,
+//		CurrVersion:         sandboxInfo.CurrVersion,
+//		Content:             sandboxInfo.Content,
+//		PicUrl:              sandboxInfo.PicUrl,
+//		OpUserId:            sandboxInfo.OpUserId,
+//		OpUserName:          sandboxInfo.OpUserName,
+//		VersionCode:         GenerateVersionCode(sandboxInfo.SandboxId, sandboxInfo.CurrVersion),
+//		IsDelete:            sandboxInfo.IsDelete,
+//		CreateTime:          sandboxInfo.CreateTime,
+//	}
+//	//沙盘草稿表信息
+//	sandboxDraftInfo := &sandbox.SandboxDraft{
+//		Name:                sandboxInfo.Name,
+//		ChartPermissionId:   sandboxInfo.ChartPermissionId,
+//		ChartPermissionName: sandboxInfo.ChartPermissionName,
+//		CurrVersion:         sandboxInfo.CurrVersion,
+//		Content:             sandboxInfo.Content,
+//		OpUserId:            sandboxInfo.OpUserId,
+//		OpUserName:          sandboxInfo.OpUserName,
+//		CreateTime:          sandboxInfo.CreateTime,
+//	}
+//
+//	//新增沙盘
+//	err = sandbox.AddNewSandbox(sandboxInfo, sandboxVersionInfo, sandboxDraftInfo)
+//	if err != nil {
+//		return
+//	}
+//	resp.Sandbox = sandboxInfo
+//	resp.VersionCode = sandboxVersionInfo.VersionCode
+//	return
+//}
 
 // UpdateSandbox 更新沙盘
-func UpdateSandbox(req request.AddAndEditSandbox, opUserId int, opUserName string, ignoreVariety bool) (resp *sandbox.SandboxSaveResp, err error, errMsg string) {
-	resp = new(sandbox.SandboxSaveResp)
-	// 获取沙盘版本信息
-	sandboxVersion, err := sandbox.GetSandboxVersionBySandboxVersionCode(req.SandboxVersionCode)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			errMsg = "找不到该版本"
-			err = errors.New(errMsg)
-		}
-		return
-	}
-	// 获取沙盘主表信息
-	sandboxInfo, err := sandbox.GetSandboxById(sandboxVersion.SandboxId)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			errMsg = "找不到该沙盘"
-			err = errors.New(errMsg)
-		}
-		return
-	}
-	//沙盘名称是否更改校验
-	var isUpdateName, isUpdateContent bool
-	if sandboxInfo.Name != utils.TrimStr(req.Name) {
-		isUpdateName = true
-	}
-
-	// 沙盘内容md5比对,不一致则代表有做更改
-	//if utils.MD5(sandboxInfo.Content) != utils.MD5(req.Content) {
-	//	isUpdateContent = true
-	//}
-	if checkoutContent(sandboxInfo.Content, req.Content) {
-		isUpdateContent = true
-	}
-
-	//如果沙盘名称和沙盘内容都没有做过修改,那么就不做保存
-	if isUpdateName == false && isUpdateContent == false {
-		return
-	}
-
-	// 获取产品权限详情
-	var permissionName string
-	if !ignoreVariety {
-		chartPermissionInfo, e := company.GetChartPermissionListById(req.ChartPermissionId)
-		if e != nil {
-			err = e
-			return
-		}
-		permissionName = chartPermissionInfo.PermissionName
-	}
-
-	//如果只更新了沙盘名称,那么只去修改最新版本的沙盘名称,而不去累计版本
-	if isUpdateName == true && isUpdateContent == false {
-		sandboxInfo.Name = utils.TrimStr(req.Name)
-		sandboxInfo.ChartPermissionId = req.ChartPermissionId
-		sandboxInfo.ChartPermissionName = permissionName
-		sandboxInfo.Content = req.Content
-		sandboxInfo.PicUrl = utils.TrimStr(req.PicUrl)
-		sandboxInfo.OpUserId = opUserId
-		sandboxInfo.OpUserName = opUserName
-		sandboxInfo.ModifyTime = time.Now()
-		var updateSandboxColumn = []string{"Name", "ChartPermissionId", "ChartPermissionName", "PicUrl", "OpUserId", "OpUserName", "ModifyTime"}
-
-		//沙盘版本表信息
-		sandboxVersionInfo, tmpErr := sandbox.GetSandboxVersionBySandbox2VersionId(sandboxInfo.SandboxId, sandboxInfo.CurrVersion)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		sandboxVersionInfo.Name = sandboxInfo.Name
-		sandboxVersionInfo.ChartPermissionId = sandboxInfo.ChartPermissionId
-		sandboxVersionInfo.ChartPermissionName = sandboxInfo.ChartPermissionName
-		sandboxVersionInfo.PicUrl = sandboxInfo.PicUrl
-		sandboxVersionInfo.OpUserId = sandboxInfo.OpUserId
-		sandboxVersionInfo.OpUserName = sandboxInfo.OpUserName
-		var updateSandboxVersionColumn = []string{"Name", "ChartPermissionId", "ChartPermissionName", "PicUrl", "OpUserId", "OpUserName"}
-
-		//沙盘草稿表信息
-		sandboxDraftInfo := &sandbox.SandboxDraft{
-			Name:                sandboxInfo.Name,
-			ChartPermissionId:   sandboxInfo.ChartPermissionId,
-			ChartPermissionName: sandboxInfo.ChartPermissionName,
-			CurrVersion:         sandboxInfo.CurrVersion,
-			Content:             sandboxInfo.Content,
-			OpUserId:            sandboxInfo.OpUserId,
-			OpUserName:          sandboxInfo.OpUserName,
-			CreateTime:          time.Now(),
-		}
-
-		//修改沙盘
-		err = sandbox.UpdateSandboxName(sandboxInfo, sandboxVersionInfo, sandboxDraftInfo, updateSandboxColumn, updateSandboxVersionColumn)
-		if err != nil {
-			return
-		}
-		resp.Sandbox = sandboxInfo
-		resp.VersionCode = sandboxVersionInfo.VersionCode
-	} else {
-		sandboxInfo.Name = utils.TrimStr(req.Name)
-		sandboxInfo.ChartPermissionId = req.ChartPermissionId
-		sandboxInfo.ChartPermissionName = permissionName
-		sandboxInfo.CurrVersion = sandboxInfo.CurrVersion + 1
-		sandboxInfo.Content = req.Content
-		sandboxInfo.PicUrl = utils.TrimStr(req.PicUrl)
-		sandboxInfo.OpUserId = opUserId
-		sandboxInfo.OpUserName = opUserName
-		sandboxInfo.ModifyTime = time.Now()
-
-		var updateSandbox = []string{"Name", "ChartPermissionId", "ChartPermissionName", "CurrVersion", "Content", "PicUrl", "OpUserId", "OpUserName", "ModifyTime"}
-
-		//沙盘版本表信息
-		sandboxVersionInfo := &sandbox.SandboxVersion{
-			Name:                sandboxInfo.Name,
-			ChartPermissionId:   sandboxInfo.ChartPermissionId,
-			ChartPermissionName: sandboxInfo.ChartPermissionName,
-			CurrVersion:         sandboxInfo.CurrVersion,
-			Content:             sandboxInfo.Content,
-			SvgData:             req.SvgData,
-			PicUrl:              sandboxInfo.PicUrl,
-			OpUserId:            sandboxInfo.OpUserId,
-			OpUserName:          sandboxInfo.OpUserName,
-			VersionCode:         GenerateVersionCode(sandboxInfo.SandboxId, sandboxInfo.CurrVersion),
-			IsDelete:            sandboxInfo.IsDelete,
-			CreateTime:          time.Now(),
-		}
-		//沙盘草稿表信息
-		sandboxDraftInfo := &sandbox.SandboxDraft{
-			Name:                sandboxInfo.Name,
-			ChartPermissionId:   sandboxInfo.ChartPermissionId,
-			ChartPermissionName: sandboxInfo.ChartPermissionName,
-			CurrVersion:         sandboxInfo.CurrVersion,
-			Content:             sandboxInfo.Content,
-			OpUserId:            sandboxInfo.OpUserId,
-			OpUserName:          sandboxInfo.OpUserName,
-			CreateTime:          time.Now(),
-		}
-
-		//修改沙盘
-		err = sandbox.UpdateSandbox(sandboxInfo, updateSandbox, sandboxVersionInfo, sandboxDraftInfo)
-		if err != nil {
-			return
-		}
-		resp.Sandbox = sandboxInfo
-		resp.VersionCode = sandboxVersionInfo.VersionCode
-	}
-	return
-}
+//func UpdateSandbox(req request.AddAndEditSandbox, opUserId int, opUserName string, ignoreVariety bool) (resp *sandbox.SandboxSaveResp, err error, errMsg string) {
+//	resp = new(sandbox.SandboxSaveResp)
+//	// 获取沙盘版本信息
+//	sandboxVersion, err := sandbox.GetSandboxVersionBySandboxVersionCode(req.SandboxVersionCode)
+//	if err != nil {
+//		if err.Error() == utils.ErrNoRow() {
+//			errMsg = "找不到该版本"
+//			err = errors.New(errMsg)
+//		}
+//		return
+//	}
+//	// 获取沙盘主表信息
+//	sandboxInfo, err := sandbox.GetSandboxById(sandboxVersion.SandboxId)
+//	if err != nil {
+//		if err.Error() == utils.ErrNoRow() {
+//			errMsg = "找不到该沙盘"
+//			err = errors.New(errMsg)
+//		}
+//		return
+//	}
+//	//沙盘名称是否更改校验
+//	var isUpdateName, isUpdateContent bool
+//	if sandboxInfo.Name != utils.TrimStr(req.Name) {
+//		isUpdateName = true
+//	}
+//
+//	// 沙盘内容md5比对,不一致则代表有做更改
+//	//if utils.MD5(sandboxInfo.Content) != utils.MD5(req.Content) {
+//	//	isUpdateContent = true
+//	//}
+//	if checkoutContent(sandboxInfo.Content, req.Content) {
+//		isUpdateContent = true
+//	}
+//
+//	//如果沙盘名称和沙盘内容都没有做过修改,那么就不做保存
+//	if isUpdateName == false && isUpdateContent == false {
+//		return
+//	}
+//
+//	// 获取产品权限详情
+//	var permissionName string
+//	if !ignoreVariety {
+//		chartPermissionInfo, e := company.GetChartPermissionListById(req.ChartPermissionId)
+//		if e != nil {
+//			err = e
+//			return
+//		}
+//		permissionName = chartPermissionInfo.PermissionName
+//	}
+//
+//	//如果只更新了沙盘名称,那么只去修改最新版本的沙盘名称,而不去累计版本
+//	if isUpdateName == true && isUpdateContent == false {
+//		sandboxInfo.Name = utils.TrimStr(req.Name)
+//		sandboxInfo.ChartPermissionId = req.ChartPermissionId
+//		sandboxInfo.ChartPermissionName = permissionName
+//		sandboxInfo.Content = req.Content
+//		sandboxInfo.PicUrl = utils.TrimStr(req.PicUrl)
+//		sandboxInfo.OpUserId = opUserId
+//		sandboxInfo.OpUserName = opUserName
+//		sandboxInfo.ModifyTime = time.Now()
+//		var updateSandboxColumn = []string{"Name", "ChartPermissionId", "ChartPermissionName", "PicUrl", "OpUserId", "OpUserName", "ModifyTime"}
+//
+//		//沙盘版本表信息
+//		sandboxVersionInfo, tmpErr := sandbox.GetSandboxVersionBySandbox2VersionId(sandboxInfo.SandboxId, sandboxInfo.CurrVersion)
+//		if tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//		sandboxVersionInfo.Name = sandboxInfo.Name
+//		sandboxVersionInfo.ChartPermissionId = sandboxInfo.ChartPermissionId
+//		sandboxVersionInfo.ChartPermissionName = sandboxInfo.ChartPermissionName
+//		sandboxVersionInfo.PicUrl = sandboxInfo.PicUrl
+//		sandboxVersionInfo.OpUserId = sandboxInfo.OpUserId
+//		sandboxVersionInfo.OpUserName = sandboxInfo.OpUserName
+//		var updateSandboxVersionColumn = []string{"Name", "ChartPermissionId", "ChartPermissionName", "PicUrl", "OpUserId", "OpUserName"}
+//
+//		//沙盘草稿表信息
+//		sandboxDraftInfo := &sandbox.SandboxDraft{
+//			Name:                sandboxInfo.Name,
+//			ChartPermissionId:   sandboxInfo.ChartPermissionId,
+//			ChartPermissionName: sandboxInfo.ChartPermissionName,
+//			CurrVersion:         sandboxInfo.CurrVersion,
+//			Content:             sandboxInfo.Content,
+//			OpUserId:            sandboxInfo.OpUserId,
+//			OpUserName:          sandboxInfo.OpUserName,
+//			CreateTime:          time.Now(),
+//		}
+//
+//		//修改沙盘
+//		err = sandbox.UpdateSandboxName(sandboxInfo, sandboxVersionInfo, sandboxDraftInfo, updateSandboxColumn, updateSandboxVersionColumn)
+//		if err != nil {
+//			return
+//		}
+//		resp.Sandbox = sandboxInfo
+//		resp.VersionCode = sandboxVersionInfo.VersionCode
+//	} else {
+//		sandboxInfo.Name = utils.TrimStr(req.Name)
+//		sandboxInfo.ChartPermissionId = req.ChartPermissionId
+//		sandboxInfo.ChartPermissionName = permissionName
+//		sandboxInfo.CurrVersion = sandboxInfo.CurrVersion + 1
+//		sandboxInfo.Content = req.Content
+//		sandboxInfo.PicUrl = utils.TrimStr(req.PicUrl)
+//		sandboxInfo.OpUserId = opUserId
+//		sandboxInfo.OpUserName = opUserName
+//		sandboxInfo.ModifyTime = time.Now()
+//
+//		var updateSandbox = []string{"Name", "ChartPermissionId", "ChartPermissionName", "CurrVersion", "Content", "PicUrl", "OpUserId", "OpUserName", "ModifyTime"}
+//
+//		//沙盘版本表信息
+//		sandboxVersionInfo := &sandbox.SandboxVersion{
+//			Name:                sandboxInfo.Name,
+//			ChartPermissionId:   sandboxInfo.ChartPermissionId,
+//			ChartPermissionName: sandboxInfo.ChartPermissionName,
+//			CurrVersion:         sandboxInfo.CurrVersion,
+//			Content:             sandboxInfo.Content,
+//			SvgData:             req.SvgData,
+//			PicUrl:              sandboxInfo.PicUrl,
+//			OpUserId:            sandboxInfo.OpUserId,
+//			OpUserName:          sandboxInfo.OpUserName,
+//			VersionCode:         GenerateVersionCode(sandboxInfo.SandboxId, sandboxInfo.CurrVersion),
+//			IsDelete:            sandboxInfo.IsDelete,
+//			CreateTime:          time.Now(),
+//		}
+//		//沙盘草稿表信息
+//		sandboxDraftInfo := &sandbox.SandboxDraft{
+//			Name:                sandboxInfo.Name,
+//			ChartPermissionId:   sandboxInfo.ChartPermissionId,
+//			ChartPermissionName: sandboxInfo.ChartPermissionName,
+//			CurrVersion:         sandboxInfo.CurrVersion,
+//			Content:             sandboxInfo.Content,
+//			OpUserId:            sandboxInfo.OpUserId,
+//			OpUserName:          sandboxInfo.OpUserName,
+//			CreateTime:          time.Now(),
+//		}
+//
+//		//修改沙盘
+//		err = sandbox.UpdateSandbox(sandboxInfo, updateSandbox, sandboxVersionInfo, sandboxDraftInfo)
+//		if err != nil {
+//			return
+//		}
+//		resp.Sandbox = sandboxInfo
+//		resp.VersionCode = sandboxVersionInfo.VersionCode
+//	}
+//	return
+//}
 
 // AddSandboxDraft 新增沙盘草稿
 func AddSandboxDraft(sandboxId int, req request.AddAndEditSandbox, opUserId int, opUserName string) (sandboxDraftInfo *sandbox.SandboxDraft, err error) {
@@ -328,29 +327,29 @@ func UpdateSandboxEditMark(sandboxId, nowUserId, status int, nowUserName string)
 }
 
 // ResetDraftToLastVersion  重置沙盘草稿至最新版本
-func ResetDraftToLastVersion(sandboxId, opUserId int, opUserName string) (sandboxDraftInfo *sandbox.SandboxDraft, err error) {
-	// 获取沙盘主表信息
-	sandboxInfo, err := sandbox.GetSandboxById(sandboxId)
-	if err != nil {
-		return
-	}
-
-	//沙盘草稿表信息
-	sandboxDraftInfo = &sandbox.SandboxDraft{
-		SandboxId:           sandboxInfo.SandboxId,
-		Name:                sandboxInfo.Name,
-		ChartPermissionId:   sandboxInfo.ChartPermissionId,
-		ChartPermissionName: sandboxInfo.ChartPermissionName,
-		Content:             sandboxInfo.Content,
-		OpUserId:            opUserId,
-		OpUserName:          opUserName,
-		CreateTime:          time.Now(),
-	}
-
-	//新增沙盘草稿
-	err = sandbox.AddSandboxDraft(sandboxDraftInfo)
-	return
-}
+//func ResetDraftToLastVersion(sandboxId, opUserId int, opUserName string) (sandboxDraftInfo *sandbox.SandboxDraft, err error) {
+//	// 获取沙盘主表信息
+//	sandboxInfo, err := sandbox.GetSandboxById(sandboxId)
+//	if err != nil {
+//		return
+//	}
+//
+//	//沙盘草稿表信息
+//	sandboxDraftInfo = &sandbox.SandboxDraft{
+//		SandboxId:           sandboxInfo.SandboxId,
+//		Name:                sandboxInfo.Name,
+//		ChartPermissionId:   sandboxInfo.ChartPermissionId,
+//		ChartPermissionName: sandboxInfo.ChartPermissionName,
+//		Content:             sandboxInfo.Content,
+//		OpUserId:            opUserId,
+//		OpUserName:          opUserName,
+//		CreateTime:          time.Now(),
+//	}
+//
+//	//新增沙盘草稿
+//	err = sandbox.AddSandboxDraft(sandboxDraftInfo)
+//	return
+//}
 
 // DeleteSandbox 删除沙盘
 func DeleteSandbox(sandboxId int) (err error) {
@@ -367,74 +366,74 @@ func DeleteSandbox(sandboxId int) (err error) {
 }
 
 // DeleteSandboxVersion 删除沙盘版本
-func DeleteSandboxVersion(sandboxVersionCode string, opUserId int) (err error, errMsg string) {
-	// 获取沙盘版本信息
-	sandboxVersion, err := sandbox.GetSandboxVersionBySandboxVersionCode(sandboxVersionCode)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			errMsg = "找不到该版本"
-			err = errors.New(errMsg)
-		}
-		return
-	}
-
-	/*key := fmt.Sprint(`crm:sandbox:edit:`, sandboxVersion.SandboxId)
-	nowOpUserId, _ := utils.Rc.RedisInt(key)
-	//如果当前有人操作,且获取当前操作人不是本人,那么不允许删除
-	if nowOpUserId > 0 && nowOpUserId != opUserId {
-		errMsg = "当前有其他人正在编辑,不允许删除该沙盘"
-		err = errors.New(errMsg)
-		return
-	}*/
-
-	markStatus, err := UpdateSandboxEditMark(sandboxVersion.SandboxId, opUserId, 2, "")
-	if err != nil {
-		errMsg = "查询标记状态失败"
-		err = errors.New("查询标记状态失败,Err:" + err.Error())
-		return
-	}
-	if markStatus.Status == 1 {
-		errMsg = fmt.Sprintf("当前%s正在编辑,不允许删除该沙盘", markStatus.Editor)
-		err = errors.New(errMsg)
-		return
-	}
-
-	// 获取沙盘主表信息
-	sandboxInfo, err := sandbox.GetSandboxById(sandboxVersion.SandboxId)
-	if err != nil {
-		return
-	}
-
-	// 删除最新版本,需要将上一个版本的给找出来覆盖
-	if sandboxVersion.CurrVersion == sandboxInfo.CurrVersion {
-		lastSandboxVersion, tmpErr := sandbox.GetLastSandboxVersionBySandbox2VersionId(sandboxInfo.SandboxId, sandboxVersion.CurrVersion)
-		if tmpErr != nil {
-			// 如果找不到,说明是删除整个沙盘,不仅仅是某个版本
-			if tmpErr.Error() == utils.ErrNoRow() {
-				sandboxInfo.IsDelete = 1
-				var updateSandboxColumn = []string{"IsDelete"}
-				err = sandboxInfo.Update(updateSandboxColumn)
-				return
-			}
-			err = tmpErr
-			return
-		} else {
-			//将当前沙盘信息修复到上一个版本
-			sandboxInfo.Content = lastSandboxVersion.Content
-			sandboxInfo.CurrVersion = lastSandboxVersion.CurrVersion
-			sandboxInfo.PicUrl = lastSandboxVersion.PicUrl
-			err = sandboxInfo.Update([]string{"Content", "CurrVersion", "PicUrl"})
-			if err != nil {
-				return
-			}
-		}
-	}
-	//将原来的版本标记删除
-	sandboxVersion.IsDelete = 1
-	err = sandboxVersion.Update([]string{"IsDelete"})
-
-	return
-}
+//func DeleteSandboxVersion(sandboxVersionCode string, opUserId int) (err error, errMsg string) {
+//	// 获取沙盘版本信息
+//	sandboxVersion, err := sandbox.GetSandboxVersionBySandboxVersionCode(sandboxVersionCode)
+//	if err != nil {
+//		if err.Error() == utils.ErrNoRow() {
+//			errMsg = "找不到该版本"
+//			err = errors.New(errMsg)
+//		}
+//		return
+//	}
+//
+//	/*key := fmt.Sprint(`crm:sandbox:edit:`, sandboxVersion.SandboxId)
+//	nowOpUserId, _ := utils.Rc.RedisInt(key)
+//	//如果当前有人操作,且获取当前操作人不是本人,那么不允许删除
+//	if nowOpUserId > 0 && nowOpUserId != opUserId {
+//		errMsg = "当前有其他人正在编辑,不允许删除该沙盘"
+//		err = errors.New(errMsg)
+//		return
+//	}*/
+//
+//	markStatus, err := UpdateSandboxEditMark(sandboxVersion.SandboxId, opUserId, 2, "")
+//	if err != nil {
+//		errMsg = "查询标记状态失败"
+//		err = errors.New("查询标记状态失败,Err:" + err.Error())
+//		return
+//	}
+//	if markStatus.Status == 1 {
+//		errMsg = fmt.Sprintf("当前%s正在编辑,不允许删除该沙盘", markStatus.Editor)
+//		err = errors.New(errMsg)
+//		return
+//	}
+//
+//	// 获取沙盘主表信息
+//	sandboxInfo, err := sandbox.GetSandboxById(sandboxVersion.SandboxId)
+//	if err != nil {
+//		return
+//	}
+//
+//	// 删除最新版本,需要将上一个版本的给找出来覆盖
+//	if sandboxVersion.CurrVersion == sandboxInfo.CurrVersion {
+//		lastSandboxVersion, tmpErr := sandbox.GetLastSandboxVersionBySandbox2VersionId(sandboxInfo.SandboxId, sandboxVersion.CurrVersion)
+//		if tmpErr != nil {
+//			// 如果找不到,说明是删除整个沙盘,不仅仅是某个版本
+//			if tmpErr.Error() == utils.ErrNoRow() {
+//				sandboxInfo.IsDelete = 1
+//				var updateSandboxColumn = []string{"IsDelete"}
+//				err = sandboxInfo.Update(updateSandboxColumn)
+//				return
+//			}
+//			err = tmpErr
+//			return
+//		} else {
+//			//将当前沙盘信息修复到上一个版本
+//			sandboxInfo.Content = lastSandboxVersion.Content
+//			sandboxInfo.CurrVersion = lastSandboxVersion.CurrVersion
+//			sandboxInfo.PicUrl = lastSandboxVersion.PicUrl
+//			err = sandboxInfo.Update([]string{"Content", "CurrVersion", "PicUrl"})
+//			if err != nil {
+//				return
+//			}
+//		}
+//	}
+//	//将原来的版本标记删除
+//	sandboxVersion.IsDelete = 1
+//	err = sandboxVersion.Update([]string{"IsDelete"})
+//
+//	return
+//}
 
 // GetSandboxVersionDetailByCode 获取沙盘的版本数据
 func GetSandboxVersionDetailByCode(sandboxVersionCode string) (sandboxVersionInfo *sandbox.SandboxVersion, err error) {
@@ -635,3 +634,221 @@ func checkoutContent(oldContent, reqContent string) (isUpdate bool) {
 
 	return
 }
+
+func sandboxClassifyHaveChild(allNode []*sandbox.SandboxClassifyItems, node *sandbox.SandboxClassifyItems) (childs []*sandbox.SandboxClassifyItems, yes bool) {
+	for _, v := range allNode {
+		if v.ParentId == node.SandboxClassifyId {
+			childs = append(childs, v)
+		}
+	}
+	if len(childs) > 0 {
+		yes = true
+	}
+	return
+}
+
+func SandboxClassifyItemsMakeTree(sysUser *system.Admin, allNode []*sandbox.SandboxClassifyItems, node *sandbox.SandboxClassifyItems) {
+
+	childs, _ := sandboxClassifyHaveChild(allNode, node) //判断节点是否有子节点并返回
+	if len(childs) > 0 {
+
+		node.Children = append(node.Children, childs[0:]...) //添加子节点
+		for _, v := range childs {                           //查询子节点的子节点,并添加到子节点
+			_, has := sandboxClassifyHaveChild(allNode, v)
+			if has {
+				SandboxClassifyItemsMakeTree(sysUser, allNode, v) //递归添加节点
+			} else {
+				childrenArr := make([]*sandbox.SandboxClassifyItems, 0)
+				v.Children = childrenArr
+			}
+		}
+	} else {
+		childrenArr := make([]*sandbox.SandboxClassifyItems, 0)
+		node.Children = childrenArr
+	}
+}
+
+// GetSandboxClassifyListForMe 获取我创建的沙盘
+func GetSandboxClassifyListForMe(adminInfo system.Admin, resp *sandbox.SandboxClassifyListResp, sandboxClassifyId int) (errMsg string, err error) {
+	rootList, err := sandbox.GetSandboxClassifyByParentId(sandboxClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	classifyAll, err := sandbox.GetSandboxClassifyByParentId(sandboxClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	sandboxAll, err := sandbox.GetSandboxInfoByAdminId(adminInfo.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	sandListMap := make(map[int][]*sandbox.SandboxClassifyItems)
+	for _, v := range sandboxAll {
+		if _, ok := sandListMap[v.SandboxClassifyId]; !ok {
+			list := make([]*sandbox.SandboxClassifyItems, 0)
+			list  = append(list, v)
+			sandListMap[v.SandboxClassifyId] = list
+		} else {
+			sandListMap[v.SandboxClassifyId] = append(sandListMap[v.SandboxClassifyId], v)
+		}
+	}
+
+	nodeAll := make([]*sandbox.SandboxClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		SandboxClassifyItemsMakeTree(&adminInfo, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+	//for k := range nodeAll {
+	//
+	//}
+	newAll := SandboxItemsMakeTree(nodeAll, sandListMap, sandboxClassifyId)
+	resp.AllNodes = newAll
+
+	return
+}
+
+// HandleNoPermissionSandbox 图表列表返回,将没有权限的图表移除
+func HandleNoPermissionSandbox(allNodes []*sandbox.SandboxClassifyItems, noPermissionChartIdMap map[int]bool) (newAllNodes []*sandbox.SandboxClassifyItems) {
+	// 移除没有权限的图表
+	newAllNodes = make([]*sandbox.SandboxClassifyItems, 0)
+	for _, node := range allNodes {
+		// 二级分类
+		tmpNodeInfo := *node
+		tmpNodeList := make([]*sandbox.SandboxClassifyItems, 0)
+		if node.Children != nil {
+			for _, chartList := range node.Children {
+				tmpInfo := *chartList
+				tmpList := make([]*sandbox.SandboxClassifyItems, 0)
+
+				if chartList.Children != nil {
+					for _, chartInfo := range chartList.Children {
+						thirdInfo := *chartInfo
+						thirdList := make([]*sandbox.SandboxClassifyItems, 0)
+						// 如果指标不可见,那么就不返回该指标
+						if _, ok := noPermissionChartIdMap[chartInfo.SandboxId]; ok {
+							continue
+						}
+						tmpList = append(tmpList, chartInfo)
+
+						if chartInfo.Children != nil {
+							for _, thirdChart := range chartInfo.Children {
+								// 如果指标不可见,那么就不返回该指标
+								if _, ok := noPermissionChartIdMap[chartInfo.SandboxId]; ok {
+									continue
+								}
+								thirdList = append(thirdList, thirdChart)
+							}
+						}
+						thirdInfo.Children = thirdList
+						tmpList = append(tmpList, &thirdInfo)
+					}
+				}
+				tmpInfo.Children = tmpList
+				tmpNodeList = append(tmpNodeList, &tmpInfo)
+			}
+		}
+		tmpNodeInfo.Children = tmpNodeList
+		newAllNodes = append(newAllNodes, &tmpNodeInfo)
+	}
+
+	return
+}
+
+// AddSandboxV2 新增沙盘
+func AddSandboxV2(req request.AddAndEditSandboxV2, opUserId int, opUserName string) (resp *sandbox.SandboxSaveResp, err error) {
+	resp = new(sandbox.SandboxSaveResp)
+	//沙盘主表信息
+	sandboxInfo := &sandbox.Sandbox{
+		Name:              utils.TrimStr(req.Name),
+		Code:              GenerateCode(),
+		Content:           req.Content,
+		MindmapData:       req.MindmapData,
+		PicUrl:            utils.TrimStr(req.PicUrl),
+		SysUserId:         opUserId,
+		SysUserName:       opUserName,
+		IsDelete:          0,
+		ModifyTime:        time.Now(),
+		CreateTime:        time.Now(),
+		SandboxClassifyId: req.SandboxClassifyId,
+		Sort:              0,
+	}
+
+	//新增沙盘
+	id, err := sandbox.AddSandbox(sandboxInfo)
+	if err != nil {
+		return
+	}
+	sandboxInfo.SandboxId = int(id)
+	resp.Sandbox = sandboxInfo
+	return
+}
+
+
+func SandboxItemsMakeTree(allNode []*sandbox.SandboxClassifyItems, sandListMap map[int][]*sandbox.SandboxClassifyItems, sandboxClassifyId int) (nodeAll []*sandbox.SandboxClassifyItems){
+	for k := range allNode {
+		if len(allNode[k].Children) > 0 {
+			SandboxItemsMakeTree(allNode[k].Children, sandListMap, sandboxClassifyId)
+			allNode = append(allNode, sandListMap[allNode[k].ParentId]...)
+			nodeAll = allNode
+		} else if k == len(allNode)-1 {
+			allNode = append(allNode, sandListMap[allNode[k].ParentId]...)
+			nodeAll = allNode
+		}
+	}
+	if len(allNode) == 0 {
+		nodeAll = append(nodeAll, sandListMap[sandboxClassifyId]...)
+	}
+	return
+}
+
+func SandboxClassifyHaveChild(allNode []*sandbox.SandboxClassifyItems, node *sandbox.SandboxClassifyItems) (childs []*sandbox.SandboxClassifyItems, yes bool) {
+	for _, v := range allNode {
+		if v.ParentId == node.SandboxClassifyId {
+			childs = append(childs, v)
+		}
+	}
+	if len(childs) > 0 {
+		yes = true
+	}
+	return
+}
+
+func SandboxClassifyItemsMakeTreeV2(sysUser *system.Admin, allNode []*sandbox.SandboxClassifyItems, node *sandbox.SandboxClassifyItems) {
+
+	childs, _ := sandboxClassifyHaveChildV2(allNode, node) //判断节点是否有子节点并返回
+	if len(childs) > 0 {
+
+		node.Children = append(node.Children, childs[0:]...) //添加子节点
+		for _, v := range childs {                           //查询子节点的子节点,并添加到子节点
+			_, has := sandboxClassifyHaveChildV2(allNode, v)
+			if has {
+				SandboxClassifyItemsMakeTreeV2(sysUser, allNode, v) //递归添加节点
+			} else {
+				//childrenArr := make([]*sandbox.SandboxClassifyItems, 0)
+				//v.Children = childrenArr
+			}
+		}
+	} else {
+		//childrenArr := make([]*sandbox.SandboxClassifyItems, 0)
+		//node.Children = childrenArr
+	}
+}
+
+func sandboxClassifyHaveChildV2(allNode []*sandbox.SandboxClassifyItems, node *sandbox.SandboxClassifyItems) (childs []*sandbox.SandboxClassifyItems, yes bool) {
+	for _, v := range allNode {
+		if v.ParentId == node.SandboxClassifyId && node.SandboxId == 0 {
+			childs = append(childs, v)
+		}
+	}
+	if len(childs) > 0 {
+		yes = true
+	}
+	return
+}

+ 131 - 0
services/smart_report/smart_report.go

@@ -0,0 +1,131 @@
+package smart_report
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/smart_report"
+	"eta/eta_api/services"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"html"
+	"strconv"
+	"time"
+)
+
+// SmartReportBuildVideoAndUpdate 生成音频
+func SmartReportBuildVideoAndUpdate(item *smart_report.SmartReport) {
+	if item == nil {
+		return
+	}
+	var err error
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("智能研报-音频生成, errMsg: %s", err.Error())
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+
+	videoUrl, videoName, videoSize, videoPlaySeconds, e := services.CreateReportVideo(item.Title, item.Content, time.Now().Local().Format(utils.FormatDateTime))
+	if e != nil {
+		err = fmt.Errorf("create audio err: %s", e.Error())
+		return
+	}
+	item.VideoUrl = videoUrl
+	item.VideoName = videoName
+	item.VideoSize = videoSize
+	item.VideoPlaySeconds = videoPlaySeconds
+	item.ModifyTime = time.Now().Local()
+	cols := []string{"VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds", "ModifyTime"}
+	if e = item.Update(cols); e != nil {
+		err = fmt.Errorf("smart report update err: %s", e.Error())
+		return
+	}
+}
+
+// UpdateSmartReportEditing 更新研报当前更新状态
+// status 枚举值 1:编辑中,0:完成编辑, 2:只做查询
+func UpdateSmartReportEditing(reportId, status, thisUserId int, thisUserName string, adminIdName map[int]string) (ret models.MarkReportResp, err error) {
+	key := fmt.Sprint(utils.CACHE_SMART_REPORT_EDITING, reportId)
+	ret.Status = 0
+	ret.Msg = "无人编辑"
+
+	opUserId, e := utils.Rc.RedisInt(key)
+	var opUser models.MarkReportItem
+	var classifyNameFirst string
+	if e != nil {
+		opUserInfoStr, tErr := utils.Rc.RedisString(key)
+		if tErr == nil {
+			tErr = json.Unmarshal([]byte(opUserInfoStr), &opUser)
+			if tErr == nil {
+				opUserId = opUser.AdminId
+			}
+		}
+	}
+
+	if opUserId > 0 && opUserId != thisUserId {
+		editor := opUser.Editor
+		if editor == "" {
+			editor = adminIdName[opUserId]
+		}
+		ret.Status = 1
+		ret.Msg = fmt.Sprintf("当前%s正在编辑报告", editor)
+		ret.Editor = editor
+		return
+	}
+
+	if status == 1 {
+		nowUser := &models.MarkReportItem{AdminId: thisUserId, Editor: thisUserName, ReportClassifyNameFirst: classifyNameFirst}
+		bt, e := json.Marshal(nowUser)
+		if e != nil {
+			err = fmt.Errorf("格式化编辑者信息失败")
+			return
+		}
+		if opUserId > 0 {
+			utils.Rc.Do("SETEX", key, int64(180), string(bt)) //3分钟缓存
+		} else {
+			utils.Rc.SetNX(key, string(bt), time.Second*60*3) //3分钟缓存
+		}
+	} else if status == 0 {
+		//清除编辑缓存
+		_ = utils.Rc.Delete(key)
+	}
+	return
+}
+
+// SmartReportElasticUpsert 新增/编辑报告es
+func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
+	if smartReportId <= 0 {
+		return
+	}
+
+	reportOB := new(smart_report.SmartReport)
+	item, e := reportOB.GetItemById(smartReportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			// 可能被删了就直接忽略掉
+			return
+		}
+		err = fmt.Errorf("获取报告失败, Err: %s", e.Error())
+		return
+	}
+
+	esReport := new(smart_report.ElasticSmartReport)
+	esReport.SmartReportId = item.SmartReportId
+	esReport.Title = item.Title
+	esReport.Abstract = item.Abstract
+	esReport.BodyContent = utils.TrimHtml(html.UnescapeString(item.Content))
+	esReport.PublishTime = item.PublishTime.Format(utils.FormatDateTime)
+	esReport.PublishState = state
+	esReport.Author = item.Author
+	esReport.ClassifyIdFirst = item.ClassifyIdFirst
+	esReport.ClassifyNameFirst = item.ClassifyNameFirst
+	esReport.ClassifyIdSecond = item.ClassifyIdSecond
+	esReport.ClassifyNameSecond = item.ClassifyNameSecond
+	esReport.StageStr = strconv.Itoa(item.Stage)
+	esReport.Frequency = item.Frequency
+	if err = services.EsAddOrEditSmartReport(utils.SmartReportIndexName, strconv.Itoa(item.SmartReportId), esReport); err != nil {
+		return
+	}
+	return
+}

+ 21 - 13
utils/config.go

@@ -82,6 +82,11 @@ var (
 
 	// ChatUrl chatGPT服务地址
 	ChatUrl string
+
+	EtaBridgeUrl       string // 桥接服务地址
+	EtaBridgeAppNameEn string // 桥接服务英文名称-鉴权用
+	EtaBridgeMd5Key    string // 桥接服务Md5密钥-鉴权用
+	EtaBridgeDesKey    string // 桥接服务Des密钥-解密数据用
 )
 
 // 微信配置信息
@@ -105,27 +110,18 @@ var (
 	EsEnglishReportIndexName       string //英文研报ES索引
 	MY_CHART_INDEX_NAME            string //研究图库(MY ETA)索引
 	EsSemanticAnalysisDocIndexName string //ES语义分析文档索引名
+	SmartReportIndexName           string //智能研报ES索引
 )
 
-// 科大讯飞--语音合成
 var (
-	//XfSTATUS_FIRST_FRAME    = 0 //第一帧标识
-	//XfSTATUS_CONTINUE_FRAME = 1 //中间帧标识
-	//XfSTATUS_LAST_FRAME     = 2 //最后一帧标识
-	//XfHost                  = "tts-api.xfyun.cn"
-	//XfMaxFontSize           = 8000
-	//XfAPPID                 string
-	//XfAPIKey                string
-	//XfAPISecret             string
-	XfHostUrl string
-	//XfOrigin  string
-	//XfVcn                   string //发言人
+	XfHostUrl string // 科大讯飞--语音合成
 )
 
 // 对象存储客户端
 var (
-	ObjectStorageClient string       // 目前有oss minio,默认oss
+	ObjectStorageClient string // 目前有oss minio,默认oss
 )
+
 // 阿里云配置
 var (
 	Bucketname       string
@@ -205,6 +201,9 @@ var (
 	MinIoRegion           string
 )
 
+// PythonUrlReport2Img 生成长图服务地址
+var PythonUrlReport2Img string
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {
@@ -318,6 +317,11 @@ func init() {
 
 		// chatGPT服务地址
 		ChatUrl = config["chat_url"]
+
+		EtaBridgeUrl = config["eta_bridge_url"]               // 桥接服务地址
+		EtaBridgeAppNameEn = config["eta_bridge_app_name_en"] // 桥接服务英文名称-鉴权用
+		EtaBridgeMd5Key = config["eta_bridge_md5_key"]        // 桥接服务Md5密钥-鉴权用
+		EtaBridgeDesKey = config["eta_bridge_des_key"]        // 桥接服务Des密钥-解密数据用
 	}
 
 	//日志配置
@@ -429,6 +433,7 @@ func init() {
 		EsReportIndexName = config["es_report_index_name"]
 		EsEnglishReportIndexName = config["es_english_report_index_name"]
 		EsSemanticAnalysisDocIndexName = config["es_semantic_analysis_doc_index_name"]
+		SmartReportIndexName = config["es_smart_report_index_name"]
 	}
 
 	CrmEtaServerUrl = config["crm_eta_server_url"]
@@ -452,6 +457,9 @@ func init() {
 		MinIoRegion = config["minio_region"]
 	}
 
+	// 生成长图服务地址
+	PythonUrlReport2Img = config["python_url_report2img"]
+
 	// 初始化ES
 	initEs()
 }

+ 15 - 2
utils/constants.go

@@ -86,8 +86,8 @@ const (
 	COMPANY_PRODUCT_RAI_NAME  = "权益"
 )
 
-var PermissionFiccClassifyArr = [...]string{"宏观经济", "化工产业", "建材产业", "有色产业", "市场策略"}
-var PermissionAllClassifyArr = [...]string{"宏观经济", "化工产业", "建材产业", "有色产业", "市场策略", "权益"}
+var PermissionFiccClassifyArr = [...]string{"宏观经济", "化工产业", "建材产业", "有色产业", "新能源", "市场策略"}
+var PermissionAllClassifyArr = [...]string{"宏观经济", "化工产业", "建材产业", "有色产业", "新能源", "市场策略", "权益"}
 
 //apply_method:申请类型:1:试用->正式,2:冻结—>试用,3:流失—>正式,4:试用延期,5:原销售申请领取流失客户,6:正式客户申请服务更新
 
@@ -219,6 +219,11 @@ const (
 	CACHE_SYNC_DEPARTMENT   = "hz_crm_eta:sync_department"   // 同步部门的缓存队列key
 	CACHE_SYNC_GROUP        = "hz_crm_eta:sync_group"        // 同步分组的缓存队列key
 	CACHE_SYNC_USER_EN_ROLE = "hz_crm_eta:sync_user_en_role" // 同步用户英文权限角色缓存队列key
+
+	CACHE_SMART_REPORT_EDITING  = "eta:smart_report:editing:" // 智能研报用户编辑中
+	CACHE_SMART_REPORT_SEND_MSG = "eta:smart_report:sending:" // 智能研报用户报告推送
+
+	CACHE_CREATE_REPORT_IMGPDF_QUEUE = "eta_report:report_img_pdf_queue" // 生成报告长图PDF队列
 )
 
 // 模板消息推送类型
@@ -280,6 +285,14 @@ const (
 	CHART_MULTIPLE_GRAPH_LINE_FEATURE_FREQUENCY          = 10 // 统计特征-频率分布图表
 )
 
+// ETA表格
+const (
+	EXCEL_DEFAULT         = 1 // 自定义excel
+	TIME_TABLE            = 2 // 时间序列表格
+	MIXED_TABLE           = 3 // 混合表格
+	CUSTOM_ANALYSIS_TABLE = 4 // 自定义分析表格
+)
+
 // 图表样式类型
 const (
 	CHART_TYPE_CURVE           = 1  //曲线图

Some files were not shown because too many files changed in this diff