Explorar o código

fix:表格详情

Roc hai 11 meses
pai
achega
e663614f29
Modificáronse 50 ficheiros con 12358 adicións e 398 borrados
  1. 1 1
      controllers/data_manage/cross_variety/chart_info.go
  2. 3 3
      controllers/data_manage/edb_info.go
  3. 3 3
      controllers/data_manage/edb_info_calculate.go
  4. 590 0
      controllers/data_manage/excel/custom_analysis.go
  5. 476 0
      controllers/data_manage/excel/custom_analysis_edb.go
  6. 690 0
      controllers/data_manage/excel/excel_classify.go
  7. 2199 0
      controllers/data_manage/excel/excel_info.go
  8. 448 0
      controllers/data_manage/excel/mixed_table.go
  9. 0 199
      controllers/data_manage/excel_info.go
  10. 2 0
      go.mod
  11. 7 0
      go.sum
  12. 18 2
      models/data_manage/edb_info.go
  13. 3 1
      models/data_manage/edb_info_calculate.go
  14. 172 0
      models/data_manage/excel/excel_classify.go
  15. 37 0
      models/data_manage/excel/excel_draft.go
  16. 142 0
      models/data_manage/excel/excel_edb_mapping.go
  17. 527 0
      models/data_manage/excel/excel_info.go
  18. 98 0
      models/data_manage/excel/excel_sheet.go
  19. 63 0
      models/data_manage/excel/excel_sheet_data.go
  20. 45 0
      models/data_manage/excel/request/excel.go
  21. 38 0
      models/data_manage/excel/request/excel_classify.go
  22. 136 0
      models/data_manage/excel/request/excel_info.go
  23. 166 0
      models/data_manage/excel/request/mixed_table.go
  24. 14 0
      models/data_manage/excel/response/excel_classify.go
  25. 89 0
      models/data_manage/excel/response/excel_info.go
  26. 33 0
      models/data_manage/excel/response/sheet.go
  27. 351 18
      routers/commentsRouter.go
  28. 3 1
      routers/router.go
  29. 51 1
      services/data/base_edb_lib.go
  30. 2 2
      services/data/chart_info.go
  31. 1 1
      services/data/correlation/chart_info.go
  32. 213 6
      services/data/edb_info.go
  33. 5 0
      services/data/edb_info_calculate.go
  34. 426 0
      services/data/excel/custom_analysis.go
  35. 623 0
      services/data/excel/custom_analysis_edb.go
  36. 475 0
      services/data/excel/excel_classify.go
  37. 1354 0
      services/data/excel/excel_info.go
  38. 286 0
      services/data/excel/excel_op.go
  39. 1301 0
      services/data/excel/mixed_table.go
  40. 1 1
      services/data/future_good/chart_info.go
  41. 3 3
      services/data/predict_edb_info.go
  42. 379 0
      services/excel/excel_to_lucky_sheet.go
  43. 461 156
      services/excel/lucky_sheet.go
  44. 49 0
      services/excel/lucky_sheet_excel.go
  45. 52 0
      services/excel/xml.go
  46. 62 0
      services/excel_info.go
  47. 85 0
      utils/calculate.go
  48. 152 0
      utils/common.go
  49. 8 0
      utils/constants.go
  50. 15 0
      utils/sort.go

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

@@ -1023,7 +1023,7 @@ func (c *ChartInfoController) Refresh() {
 	}
 
 	// 批量刷新
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, false)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, false, false)
 	if err != nil {
 		return
 	}

+ 3 - 3
controllers/data_manage/edb_info.go

@@ -2406,7 +2406,7 @@ func (this *EdbInfoController) EdbInfoEdit() {
 	//添加es
 	data.AddOrEditEdbInfoToEs(req.EdbInfoId)
 	// 修改关联的预测指标基础信息
-	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfo)
+	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfo, req.Frequency, req.Unit)
 
 	// 添加钢联指标更新日志
 	if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
@@ -2585,7 +2585,7 @@ func (this *EdbInfoController) EdbInfoRefresh() {
 		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
 		return
 	}
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, false)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, false, false)
 	if err != nil {
 		br.Msg = "刷新失败"
 		br.ErrMsg = "刷新指标失败,EdbInfoRefresh Err:" + err.Error()
@@ -3259,7 +3259,7 @@ func (this *EdbInfoController) EdbInfoAllRefresh() {
 		br.ErrMsg = "数据已被删除,请刷新页面,edbInfoId:" + strconv.Itoa(edbInfoId)
 		return
 	}
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true, false)
 	if err != nil {
 		br.Msg = "刷新失败"
 		br.ErrMsg = "刷新指标失败,EdbInfoRefreshAllFromBase Err:" + err.Error()

+ 3 - 3
controllers/data_manage/edb_info_calculate.go

@@ -412,7 +412,7 @@ func (this *ChartInfoController) CalculateEdit() {
 	data.AddOrEditEdbInfoToEs(req.EdbInfoId)
 
 	// 修改关联的预测指标基础信息
-	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfoDetail)
+	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfoDetail, req.Frequency, req.Unit)
 
 	br.Ret = 200
 	br.Success = true
@@ -813,7 +813,7 @@ func (this *ChartInfoController) CalculateBatchEdit() {
 	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
 
 	// 修改关联的预测指标基础信息
-	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfo)
+	go data.ModifyPredictEdbBaseInfoBySourceEdb(edbInfo, req.Frequency, req.Unit)
 
 	br.Ret = 200
 	br.Success = true
@@ -962,7 +962,7 @@ func (this *ChartInfoController) CalculateBatchReset() {
 	//}
 
 	// 重新计算
-	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true)
+	err, isAsync := data.EdbInfoRefreshAllFromBaseV2(edbInfoId, true, false)
 	if err != nil {
 		fmt.Println(edbInfoId, "RefreshEdbCalculateData err", time.Now())
 		br.Msg = "重新计算失败"

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

@@ -0,0 +1,590 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	excelModel "eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/models/data_manage/excel/response"
+	"eta/eta_mobile/services"
+	"eta/eta_mobile/services/data/excel"
+	"eta/eta_mobile/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
+}

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

@@ -0,0 +1,476 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/data_manage"
+	excelModel "eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/services/data/excel"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// EdbList
+// @Title 指标列表
+// @Description 指标列表
+// @Param   ExcelInfoId   query   int  true       "excel的id"
+// @Param   Keyword   query   string  false	"指标关键词"
+// @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
+	}
+	keyword := c.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+	if keyword != "" {
+		keyword = fmt.Sprint("%", keyword, "%")
+	}
+
+	// 获取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.GetExcelEdbMappingItemByExcelInfoIdOrKeyword(excelInfo.ExcelInfoId, keyword)
+	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, req.Frequency, req.Unit)
+
+	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
+//	}
+//}

+ 690 - 0
controllers/data_manage/excel/excel_classify.go

@@ -0,0 +1,690 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	response2 "eta/eta_mobile/models/data_manage/excel/response"
+	excel2 "eta/eta_mobile/services/data/excel"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"time"
+)
+
+// ExcelClassifyController ETA表格分类
+type ExcelClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// 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() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		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
+	}
+
+	classifyList, err := excel.GetExcelClassifyBySource(source)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	// 获取二级分类
+	// 获取三级分类
+	// 根据来源获取所有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][]*excel.ExcelClassifyItems)
+	for _, v := range allExcelInfo {
+		ExcelInfoMap[v.ExcelClassifyId] = append(ExcelInfoMap[v.ExcelClassifyId], v)
+	}
+	classifyMap := make(map[int][]*excel.ExcelClassifyItems)
+	for _, v := range classifyList {
+		if existItems, ok := ExcelInfoMap[v.ExcelClassifyId]; ok {
+			v.Children = existItems
+		}
+	}
+
+	for _, v := range classifyList {
+		if v.ParentId > 0 {
+			classifyMap[v.ParentId] = append(classifyMap[v.ParentId], v)
+		}
+	}
+	//组装三级分类
+	for key, classify := range classifyList {
+		subList, ok := classifyMap[classify.ExcelClassifyId]
+		if ok {
+			classifyList[key].Children = append(classifyList[key].Children, subList...)
+			sort.Slice(classifyList[key].Children, func(i, j int) bool {
+				return excel.ExcelClassifyItemBySort(classifyList[key].Children[i], classifyList[key].Children[j])
+			})
+		}
+	}
+
+	nodeAll := make([]*excel.ExcelClassifyItems, 0)
+	for _, v := range classifyList {
+		if v.ParentId == 0 {
+			sort.Slice(v.Children, func(i, j int) bool { return excel.ExcelClassifyItemBySort(v.Children[i], v.Children[j]) })
+			nodeAll = append(nodeAll, v)
+		}
+	}
+	resp := response2.ExcelClassifyListResp{
+		AllNodes: nodeAll,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// 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() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	source, _ := this.GetInt("Source")
+	if source <= 0 {
+		source = utils.EXCEL_DEFAULT
+	}
+	classifyList, err := excel.GetExcelClassifyBySource(source)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	// 获取二级分类
+	// 获取三级分类
+	classifyMap := make(map[int][]*excel.ExcelClassifyItems)
+	for _, v := range classifyList {
+		if v.ParentId > 0 {
+			classifyMap[v.ParentId] = append(classifyMap[v.ParentId], v)
+		}
+	}
+	//组装三级分类
+	for key, classify := range classifyList {
+		subList, ok := classifyMap[classify.ExcelClassifyId]
+		if ok {
+			classifyList[key].Children = append(classifyList[key].Children, subList...)
+		}
+	}
+
+	nodeAll := make([]*excel.ExcelClassifyItems, 0)
+	for _, v := range classifyList {
+		if v.ParentId == 0 {
+			nodeAll = append(nodeAll, v)
+		}
+	}
+	resp := response2.ExcelClassifyListResp{
+		AllNodes: nodeAll,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// AddExcelClassify
+// @Title 新增excel表格分类
+// @Description 新增excel表格分类接口
+// @Param	request	body request.AddExcelClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /excel_classify/add [post]
+func (this *ExcelClassifyController) AddExcelClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.AddExcelClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ExcelClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	source := req.Source
+	if source <= 0 {
+		source = utils.EXCEL_DEFAULT
+	}
+
+	// 获取同级分类下存在同名分类的数量
+	count, err := excel.GetExcelClassifyCount(req.ExcelClassifyName, req.ParentId, source)
+	if err != nil {
+		br.Msg = "判断名称是否已存在失败"
+		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "分类名称已存在,请重新输入"
+		br.IsSendEmail = false
+		return
+	}
+	//获取该层级下最大的排序数
+	maxSort, err := excel2.GetExcelClassifyMaxSort(req.ParentId, req.Source)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "查询排序信息失败,Err:" + err.Error()
+		return
+	}
+	level := 1
+	// 查询父级分类是否存在
+	if req.ParentId > 0 {
+		var parent *excel.ExcelClassify
+		parent, err = excel.GetExcelClassifyById(req.ParentId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "父级分类不存在"
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = "查询父级分类信息失败,Err:" + err.Error()
+			return
+		}
+		level = parent.Level + 1
+	}
+	// 入库
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	classify := &excel.ExcelClassify{
+		//ExcelClassifyId:   0,
+		ExcelClassifyName: req.ExcelClassifyName,
+		ParentId:          req.ParentId,
+		Source:            source,
+		SysUserId:         this.SysUser.AdminId,
+		SysUserRealName:   this.SysUser.RealName,
+		Level:             level,
+		UniqueCode:        utils.MD5(utils.EXCEL_DATA_PREFIX + "_" + timestamp),
+		Sort:              maxSort + 1,
+		CreateTime:        time.Now(),
+		ModifyTime:        time.Now(),
+	}
+	_, err = excel.AddExcelClassify(classify)
+	if err != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+}
+
+// EditExcelClassify
+// @Title 修改excel表格分类
+// @Description 修改excel表格分类
+// @Param	request	body request.EditExcelClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /excel_classify/edit [post]
+func (this *ExcelClassifyController) EditExcelClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.EditExcelClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ExcelClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ExcelClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	item, err := excel.GetExcelClassifyById(req.ExcelClassifyId)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.Msg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 名字一致的话,直接返回成功
+	if item.ExcelClassifyName == req.ExcelClassifyName {
+		br.Ret = 200
+		br.Msg = "保存成功"
+		br.Success = true
+		return
+	}
+
+	// 获取同级分类下存在同名分类的数量
+	count, err := excel.GetExcelClassifyCount(req.ExcelClassifyName, item.ParentId, item.Source)
+	if err != nil {
+		br.Msg = "判断名称是否已存在失败"
+		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "分类名称已存在,请重新输入"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 修改分类名称
+	item.ExcelClassifyName = req.ExcelClassifyName
+	err = item.Update([]string{"ExcelClassifyName"})
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "修改成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// DeleteExcelClassifyCheck
+// @Title 删除表格检测接口
+// @Description 删除表格检测接口
+// @Param	request	body request.ExcelClassifyDeleteCheckReq true "type json string"
+// @Success 200 {object} response.ExcelClassifyDeleteCheckResp
+// @router /excel_classify/delete/check [post]
+func (this *ExcelClassifyController) DeleteExcelClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.ExcelClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ExcelClassifyId < 0 && req.ExcelInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+
+	// 校验是否存在该分类
+	ExcelClassifyInfo, err := excel.GetExcelClassifyById(req.ExcelClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "该分类不存在"
+			br.ErrMsg = "该分类不存在"
+			return
+		}
+		br.Msg = "删除失败"
+		br.ErrMsg = "查询该分类失败,Err:" + err.Error()
+		return
+	}
+	if ExcelClassifyInfo == nil {
+		br.Msg = "该分类不存在"
+		br.ErrMsg = "该分类不存在"
+		br.IsSendEmail = false
+		return
+	}
+	//删除分类
+	if req.ExcelClassifyId > 0 && req.ExcelInfoId == 0 {
+		//判断表格分类下,是否含有表格
+		count, err := excel.GetExcelInfoCountByClassifyId(req.ExcelClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有指标失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联表格不可删除"
+		} else {
+			childClassify, e := excel.GetChildClassifyById(req.ExcelClassifyId)
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类信息失败, GetEdbClassify,Err:" + e.Error()
+				return
+			}
+			if len(childClassify) > 0 {
+				var classifyIds []int
+				for _, v := range childClassify {
+					classifyIds = append(classifyIds, v.ExcelClassifyId)
+				}
+				condition := fmt.Sprintf(` AND excel_classify_id IN (%s) `, utils.GetOrmInReplace(len(classifyIds)))
+				var pars []interface{}
+				pars = append(pars, classifyIds)
+				childCount, err := excel.GetExcelInfoCountByCondition(condition, pars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "查询分类下表格数量失败,Err:" + err.Error()
+					return
+				}
+
+				if childCount > 0 {
+					deleteStatus = 1
+					tipsMsg = "该分类下关联表格不可删除"
+				}
+			}
+		}
+	}
+
+	//if deleteStatus != 1 && req.ExcelInfoId == 0 {
+	//	classifyCount, err := data_manage.GetExcelClassifyCountByClassifyId(req.ExcelClassifyId)
+	//	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 := response2.ExcelClassifyDeleteCheckResp{
+		DeleteStatus: deleteStatus,
+		TipsMsg:      tipsMsg,
+	}
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// DeleteExcelClassify
+// @Title 删除表格分类/表格
+// @Description 删除表格分类/表格接口
+// @Param	request	body request.DeleteExcelClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /excel_classify/delete [post]
+func (this *ExcelClassifyController) DeleteExcelClassify() {
+	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.DeleteExcelClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ExcelClassifyId < 0 && req.ExcelInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	//删除分类
+	if req.ExcelClassifyId > 0 && req.ExcelInfoId == 0 {
+		//判断是否含有指标
+		count, err := excel.GetExcelInfoCountByClassifyId(req.ExcelClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			br.Msg = "该目录下存在关联ETA表格,不可删除"
+			br.IsSendEmail = false
+			return
+		} else {
+			childClassify, e := excel.GetChildClassifyById(req.ExcelClassifyId)
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类信息失败, GetEdbClassify,Err:" + e.Error()
+				return
+			}
+			if len(childClassify) > 0 {
+				var classifyIds []int
+				for _, v := range childClassify {
+					classifyIds = append(classifyIds, v.ExcelClassifyId)
+				}
+				condition := fmt.Sprintf(` AND excel_classify_id IN (%s) `, utils.GetOrmInReplace(len(classifyIds)))
+				var pars []interface{}
+				pars = append(pars, classifyIds)
+				childCount, err := excel.GetExcelInfoCountByCondition(condition, pars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "查询分类下表格数量失败,Err:" + err.Error()
+					return
+				}
+
+				if childCount > 0 {
+					br.Msg = "该目录下存在关联ETA表格,不可删除"
+					br.IsSendEmail = false
+					return
+				}
+			}
+		}
+		classifyItem, err := excel.GetExcelClassifyById(req.ExcelClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				br.ErrMsg = "分类不存在"
+			}
+			return
+		}
+		classifyItem.IsDelete = 1
+		err = classifyItem.Update([]string{"IsDelete"})
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	resp := response2.AddExcelInfoResp{}
+	//删除表格
+	if req.ExcelInfoId > 0 {
+		excelInfo, err := excel.GetExcelInfoById(req.ExcelInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "表格已删除,请刷新页面"
+				br.ErrMsg = "表格已删除,请刷新页面"
+				return
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,获取指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+		if excelInfo == nil {
+			br.Msg = "表格已删除,请刷新页面"
+			br.IsSendEmail = false
+			return
+		}
+
+		// 删除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 *excel.ExcelInfo
+			var condition string
+			var pars []interface{}
+			condition += " AND excel_classify_id=? "
+			pars = append(pars, excelInfo.ExcelClassifyId)
+
+			condition += " AND (sort>? OR (sort=? AND excel_info_id<?) ) "
+			pars = append(pars, excelInfo.Sort, excelInfo.Sort, excelInfo.ExcelInfoId)
+			nextItem, err = excel.GetNextExcelInfoByCondition(condition, pars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
+				return
+			}
+
+			// 如果没找到,那么查找下一个分类的第一个表格
+			if nextItem == nil {
+				currClassifyInfo, err := excel.GetExcelClassifyById(excelInfo.ExcelClassifyId)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "获取当前表格分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				nextItem, err = excel.GetNextExcelInfo(excelInfo.ExcelClassifyId, currClassifyInfo.Sort, currClassifyInfo.Source)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "获取下一级表格信息失败,Err:" + err.Error()
+					return
+				}
+			}
+
+			// 如果找到下一个表格了,那么就返回
+			if nextItem != nil {
+				resp = response2.AddExcelInfoResp{
+					ExcelInfoId: nextItem.ExcelInfoId,
+					UniqueCode:  nextItem.UniqueCode,
+				}
+			}
+		}
+
+		//新增操作日志
+		//{
+		//	ExcelLog := new(data_manage.ExcelInfoLog)
+		//	ExcelLog.ExcelName = ExcelInfo.ExcelName
+		//	ExcelLog.ExcelInfoId = req.ExcelInfoId
+		//	ExcelLog.ExcelClassifyId = ExcelInfo.ExcelClassifyId
+		//	ExcelLog.SysUserId = sysUser.AdminId
+		//	ExcelLog.SysUserRealName = sysUser.RealName
+		//	ExcelLog.UniqueCode = ExcelInfo.UniqueCode
+		//	ExcelLog.CreateTime = time.Now()
+		//	ExcelLog.Content = string(this.Ctx.Input.RequestBody)
+		//	ExcelLog.Status = "删除表格"
+		//	ExcelLog.Method = this.Ctx.Input.URI()
+		//	go data_manage.AddExcelInfoLog(ExcelLog)
+		//}
+	}
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// ExcelClassifyMove
+// @Title 表格分类移动接口
+// @Description 表格分类移动接口
+// @Success 200 {object} request.MoveExcelClassifyReq
+// @router /excel_classify/move [post]
+func (this *ExcelClassifyController) ExcelClassifyMove() {
+	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.MoveExcelClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ClassifyId <= 0 && req.ExcelInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "请选择拖动目标,分类目录或者指标"
+		return
+	}
+	//判断分类是否存在
+	err, errMsg := excel2.MoveExcelClassify(req)
+	if err != nil {
+		br.Msg = err.Error()
+		br.ErrMsg = errMsg
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+	br.Msg = "移动成功"
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2199 - 0
controllers/data_manage/excel/excel_info.go


+ 448 - 0
controllers/data_manage/excel/mixed_table.go

@@ -0,0 +1,448 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/services/data"
+	excel2 "eta/eta_mobile/services/data/excel"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// GetSystemDate
+// @Title 获取系统日期(包含计算日期)
+// @Description 获取系统日期(包含计算日期)
+// @Param	request	body request.MixedTableCellDataReq true "type json string"
+// @router /excel_info/get_system_date [post]
+func (c *ExcelInfoController) GetSystemDate() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.MixedTableCellDataReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	var config request.EdbDateConf
+	err = json.Unmarshal([]byte(req.Value), &config)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	date, _, err, errMsg := excel2.HandleDate(req.DataTimeType, req.Value)
+	if err != nil {
+		br.Msg = "获取系统日期失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取系统日期失败,Err:" + err.Error()
+		return
+	}
+	if req.DataTimeType == request.EdbDateDT {
+		edbInfo, err := data_manage.GetEdbInfoById(config.EdbInfoId)
+		if err != nil {
+			br.Msg = "获取指标信息失败!"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+
+		dataList := make([]*data_manage.EdbDataList, 0)
+		switch edbInfo.EdbInfoType {
+		case 0:
+			dataList, _ = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+		case 1:
+			_, dataList, _, _, _, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+		default:
+			br.Msg = "指标类型异常!"
+			br.ErrMsg = "指标类型异常,Err:" + strconv.Itoa(edbInfo.EdbInfoType)
+			return
+		}
+
+		if req.DataTime == `` { //选择前移几期数
+			date, err = excel2.GetEdbDateByMoveForward(req.Value, dataList)
+			if err != nil {
+				br.Msg = "查询指标前移日期失败!"
+				br.ErrMsg = "查询指标前移日期失败,Err:" + err.Error()
+				return
+			}
+		} else {
+			date = req.DataTime //选择表格中的日期
+		}
+		if date != "" {
+			// 开始做日期变换
+			date, err = excel2.HandleMixTableDateChange(date, req.Value)
+			if err != nil {
+				br.Msg = "日期变换失败!"
+				br.ErrMsg = "日期变换失败,Err:" + err.Error()
+				return
+			}
+		}
+	}
+
+	type resp struct {
+		Date string
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取系统日期成功"
+	br.Data = resp{
+		Date: date,
+	}
+}
+
+// CalculateData
+// @Title 公式计算(混合表格)
+// @Description 公式计算(混合表格)
+// @Param	request	body request.CalculateConf true "type json string"
+// @router /excel_info/mixed/calculate [post]
+func (c *ExcelInfoController) CalculateData() {
+	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
+	}
+	requestBody := string(c.Ctx.Input.RequestBody)
+	var req request.CalculateConf
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
+	if err != nil {
+		br.Msg = "获取指标信息失败!"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	dataList := make([]*data_manage.EdbDataList, 0)
+	switch edbInfo.EdbInfoType {
+	case 0:
+		dataList, _ = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+	case 1:
+		_, dataList, _, _, _, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+	default:
+		br.Msg = "指标类型异常!"
+		br.ErrMsg = "指标类型异常,Err:" + strconv.Itoa(edbInfo.EdbInfoType)
+		return
+	}
+
+	//获取所有数据,计算所有数据
+	//获取部分数据,计算部分数据
+	// BaseCalculate 数据计算的结构体
+	type BaseCalculate struct {
+		DataList      []*data_manage.EdbDataList
+		Frequency     string `description:"需要转换的频度"`
+		Formula       interface{}
+		Calendar      string `description:"公历/农历"`
+		MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
+		MoveFrequency string `description:"移动频度"`
+		FromFrequency string `description:"来源的频度"`
+		Source        int    `description:"1:累计值转月;2:累计值转季;3:同比值;4:同差值;5:N数值移动平均数计算;6:环比值;7:环差值;8:升频;9:降频;10:时间移位;11:超季节性;12:年化;13:累计值;14:累计值年初至今;15:指数修匀;16:日均值"`
+	}
+
+	req2 := &BaseCalculate{
+		DataList:      dataList,
+		Frequency:     req.Frequency,
+		Formula:       req.Formula,
+		Calendar:      req.Calendar,
+		MoveType:      req.MoveType,
+		MoveFrequency: req.MoveFrequency,
+		FromFrequency: edbInfo.Frequency,
+		Source:        req.Source,
+	}
+
+	// 调用指标库去更新
+	reqJson, tmpErr := json.Marshal(req2)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	respItem, tmpErr := data.BaseCalculate(string(reqJson))
+	if tmpErr != nil {
+		br.Msg = "计算指标失败"
+		br.ErrMsg = tmpErr.Error()
+		return
+	}
+	if respItem.Ret != 200 {
+		br.Msg = respItem.Msg
+		br.ErrMsg = respItem.ErrMsg
+		return
+	}
+
+	// 数据处理成需要返回的样式
+	num := 5
+	lenDate := len(respItem.Data.DateList)
+	if lenDate < 5 {
+		num = lenDate
+	}
+
+	dataListResp := make([]*data_manage.EdbDataList, 0)
+
+	var newDate string
+	var showValue string
+	if req.DataTime == `` { //选择前移几期数
+		newDate, err = excel2.GetEdbDateByMoveForwardByDateList(requestBody, respItem.Data.DateList)
+		if err != nil {
+			return
+		}
+	} else {
+		newDate = req.DataTime //选择表格中的日期
+		if strings.Count(newDate, "-") == 1 {
+			newDate = newDate + "-01"
+			// 查找这个月早的时间作为起始时间
+			for _, v := range respItem.Data.DateList {
+				if v >= newDate {
+					newDate = v
+				}
+			}
+		}
+	}
+	// 开始做日期变换
+	newDate, err = excel2.HandleMixTableDateChange(newDate, requestBody)
+	if err != nil {
+		return
+	}
+	if req.DataTime == `` {
+		canAdd := false
+		for i := 1; i <= lenDate; i++ {
+			date := respItem.Data.DateList[lenDate-i]
+			val, ok := respItem.Data.DataMap[date]
+			if !ok {
+				continue
+			}
+			if date == newDate {
+				showValue = utils.FormatMixTableDataShowValue(val)
+				canAdd = true
+			}
+			if canAdd && len(dataListResp) <= num {
+				dataListResp = append(dataListResp, &data_manage.EdbDataList{
+					Value:    val,
+					DataTime: date,
+				})
+			}
+		}
+	} else {
+		// todo 如果选择了表格中的日期应该如何处理
+		if val, ok := respItem.Data.DataMap[newDate]; ok {
+			showValue = utils.FormatMixTableDataShowValue(val)
+			for i, tmpDate := range respItem.Data.DateList {
+				if tmpDate == newDate {
+					if i+3 <= lenDate {
+						t1Date := respItem.Data.DateList[i+2]
+						if tmpVal, ok2 := respItem.Data.DataMap[t1Date]; ok2 {
+							// 当前日期
+							dataListResp = append(dataListResp, &data_manage.EdbDataList{
+								Value:    tmpVal,
+								DataTime: t1Date,
+							})
+						}
+					}
+
+					if i+2 <= lenDate {
+						t1Date := respItem.Data.DateList[i+1]
+						if tmpVal, ok2 := respItem.Data.DataMap[t1Date]; ok2 {
+							// 当前日期
+							dataListResp = append(dataListResp, &data_manage.EdbDataList{
+								Value:    tmpVal,
+								DataTime: t1Date,
+							})
+						}
+					}
+
+					// 当前日期
+					dataListResp = append(dataListResp, &data_manage.EdbDataList{
+						Value:    val,
+						DataTime: newDate,
+					})
+					if i >= 1 {
+						t1Date := respItem.Data.DateList[i-1]
+						if tmpVal, ok2 := respItem.Data.DataMap[t1Date]; ok2 {
+							// 当前日期
+							dataListResp = append(dataListResp, &data_manage.EdbDataList{
+								Value:    tmpVal,
+								DataTime: t1Date,
+							})
+						}
+					}
+					if i >= 2 {
+						t1Date := respItem.Data.DateList[i-2]
+						if tmpVal, ok2 := respItem.Data.DataMap[t1Date]; ok2 {
+							// 当前日期
+							dataListResp = append(dataListResp, &data_manage.EdbDataList{
+								Value:    tmpVal,
+								DataTime: t1Date,
+							})
+						}
+					}
+				}
+			}
+		}
+	}
+	resp := data_manage.BeforeAndAfterDateDataResp{
+		List:      dataListResp,
+		Date:      newDate,
+		ShowValue: showValue,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "计算成功"
+	br.Data = resp
+}
+
+// GetMixDateCalculate
+// @Title 获取混合表格日期计算
+// @Description 获取混合表格日期计算
+// @Param	request	body request.MixedTableCellDataReq true "type json string"
+// @router /excel_info/mixed/date_calculate [post]
+func (c *ExcelInfoController) GetMixDateCalculate() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.MixedDateCalculateReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	valMap := make(map[string]int)
+	for _, v := range req.DateList {
+		// 查找单元格数据
+		// 如果不是基础计算单元格,直接返回
+		_, err = time.ParseInLocation(utils.FormatDate, v.Date, time.Local)
+		if err != nil {
+			br.Msg = "日期计算失败!"
+			br.ErrMsg = fmt.Sprintf("%s 的单元格非日期类型, Err: %s", v.Date, err.Error())
+			return
+		}
+		// todo 把日期转换成excel里的天数
+		realDiffDay := utils.GetDaysDiff1900(v.Date)
+
+		valMap[strings.ToUpper(v.Tag)] = realDiffDay
+	}
+
+	// 计算
+	val, errMsg, err := excel2.DateCalculateFormula(valMap, strings.ToUpper(req.Formula))
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	showValue := utils.FormatMixTableDataShowValue(val)
+
+	type resp struct {
+		ShowValue string
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.Data = resp{
+		ShowValue: showValue,
+	}
+}
+
+// GetBaseEdbInfo
+// @Title 获取指标的基本信息
+// @Description 获取指标的基本信息
+// @Param	EdbInfoIds	string  "指标ID用英文逗号分割"
+// @router /excel_info/base_edb_info [get]
+func (c *ExcelInfoController) GetBaseEdbInfo() {
+	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
+	}
+	edbInfoIds := c.GetString("EdbInfoIds")
+	if edbInfoIds == "" {
+		br.Msg = "请输入指标ID"
+		return
+	}
+	ids := strings.Split(edbInfoIds, ",")
+	edbIds := make([]int, 0)
+	for _, v := range ids {
+		id, err := strconv.Atoi(v)
+		if err != nil {
+			br.Msg = "指标ID格式错误"
+			return
+		}
+		edbIds = append(edbIds, id)
+	}
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(edbIds)
+	if err != nil {
+		br.Msg = "获取指标信息失败!"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+	list := make([]*data_manage.BaseEdbNameItem, 0)
+	for _, v := range edbInfoList {
+		tmp := new(data_manage.BaseEdbNameItem)
+
+		tmp.EdbInfoId = v.EdbInfoId
+		tmp.EdbInfoType = v.EdbInfoType
+		tmp.EdbCode = v.EdbCode
+		tmp.EdbName = v.EdbName
+		tmp.Source = v.Source
+		tmp.SourceName = v.SourceName
+		tmp.Frequency = v.Frequency
+		tmp.Unit = v.Unit
+		list = append(list, tmp)
+	}
+	resp := data_manage.BaseEdbInfoResp{
+		List: list,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询成功"
+	br.Data = resp
+}

+ 0 - 199
controllers/data_manage/excel_info.go

@@ -1,199 +0,0 @@
-package data_manage
-
-import (
-	"eta/eta_mobile/controllers"
-	"eta/eta_mobile/models"
-	"eta/eta_mobile/models/data_manage"
-	"eta/eta_mobile/models/data_manage/response"
-	"eta/eta_mobile/services/excel"
-	"eta/eta_mobile/utils"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/shopspring/decimal"
-)
-
-// ExcelInfoController ETA表格管理
-type ExcelInfoController struct {
-	controllers.BaseAuthController
-}
-
-// List
-// @Title ETA表格列表接口
-// @Description ETA表格列表接口
-// @Param   PageSize   query   int  true       "每页数据条数"
-// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   ExcelClassifyId   query   int  true       "分类id"
-// @Param   Keyword   query   string  true       "搜索关键词"
-// @Param   AdminId   query   int  false       "创建人id"
-// @Success 200 {object} response.ExcelListResp
-// @router /excel_info/list [get]
-func (this *ExcelInfoController) List() {
-	br := new(models.BaseResponse).Init()
-	defer func() {
-		this.Data["json"] = br
-		this.ServeJSON()
-	}()
-	sysUser := this.SysUser
-	if sysUser == nil {
-		br.Msg = "请登录"
-		br.ErrMsg = "请登录,SysUser Is Empty"
-		br.Ret = 408
-		return
-	}
-
-	excelClassifyId, _ := this.GetInt("ExcelClassifyId")
-
-	pageSize, _ := this.GetInt("PageSize")
-	currentIndex, _ := this.GetInt("CurrentIndex")
-	keyword := this.GetString("Keyword")
-	adminId, _ := this.GetInt("AdminId")
-
-	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 excelClassifyId > 0 {
-		_, err := data_manage.GetExcelClassifyById(excelClassifyId)
-		if err != nil && err.Error() != utils.ErrNoRow() {
-			br.Msg = "获取表格信息失败"
-			br.ErrMsg = "获取信息失败,GetExcelClassify,Err:" + err.Error()
-			return
-		}
-		condition += " AND excel_classify_id = ? "
-		pars = append(pars, excelClassifyId)
-	}
-	if keyword != "" {
-		condition += ` AND  ( excel_name LIKE ? )`
-		pars = append(pars, `%`+keyword+`%`)
-	}
-	if adminId > 0 {
-		condition += " AND sys_user_id = ? "
-		pars = append(pars, adminId)
-	}
-	//获取表格信息
-	list, err := data_manage.GetNoContentExcelListByCondition(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 || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
-		list = make([]*data_manage.MyExcelInfoList, 0)
-	}
-	// 总数据量
-	dataCount, err := data_manage.GetExcelListCountByCondition(condition, pars)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = "获取表格列表信息失败"
-		br.ErrMsg = "获取表格列表数据总数失败,Err:" + err.Error()
-		return
-	}
-	page = paging.GetPaging(currentIndex, pageSize, dataCount)
-
-	resp := response.ExcelListResp{
-		Paging: page,
-		List:   list,
-	}
-	br.Ret = 200
-	br.Success = true
-	br.Msg = "获取成功"
-	br.Data = resp
-}
-
-// GetExcelTableData
-// @Title 获取excel表格的table数据
-// @Description 获取excel表格的table数据接口
-// @Param   UniqueCode   query   string  true       "表格code"
-// @Param   FromScene   query   int  true       "场景来源,1:智能研报,2:研报列表;3:英文研报;4:中文PPT;5:英文PPT"
-// @Success 200 {object} response.ExcelTableDetailResp
-// @router /excel_info/table_data [get]
-func (this *ExcelInfoController) GetExcelTableData() {
-	br := new(models.BaseResponse).Init()
-	defer func() {
-		this.Data["json"] = br
-		this.ServeJSON()
-	}()
-	sysUser := this.SysUser
-	if sysUser == nil {
-		br.Msg = "请登录"
-		br.ErrMsg = "请登录,SysUser Is Empty"
-		br.Ret = 408
-		return
-	}
-	uniqueCode := this.GetString("UniqueCode")
-	fromScene, _ := this.GetInt("FromScene", 0)
-
-	var err error
-	if uniqueCode == `` {
-		br.Msg = "请选择表格"
-		br.ErrMsg = "UniqueCode未传"
-		br.IsSendEmail = false
-		return
-	}
-	//获取eta表格信息
-	excelInfo, err := data_manage.GetExcelInfoByUniqueCode(uniqueCode)
-	if err != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取ETA表格信息失败,Err:" + err.Error()
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "ETA表格被删除,请刷新页面"
-			br.ErrMsg = "ETA表格被删除,请刷新页面,Err:" + err.Error()
-			br.IsSendEmail = false
-		}
-		return
-	}
-
-	luckySheetData, err := excel.GetLuckySheetData(excelInfo.Content)
-	if err != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取excel数据失败,Err:" + err.Error()
-		return
-	}
-	tableData, err := luckySheetData.GetTableDataByLuckySheetDataStr()
-	if err != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "转换成table失败,Err:" + err.Error()
-		return
-	}
-
-	tableData = excel.HandleTableCell(tableData)
-	config := response.ExcelTableDetailConfigResp{
-		FontSize: 9,
-	}
-
-	// 获取配置的字体大小
-	confName := models.FromSceneMap[fromScene]
-	if confName != `` {
-		busConf, err := models.GetBusinessConfByKey(confName)
-		if err == nil {
-			sizeDeci, err := decimal.NewFromString(busConf.ConfVal)
-			if err == nil {
-				config.FontSize = int(sizeDeci.IntPart())
-			}
-		}
-	}
-
-	resp := response.ExcelTableDetailResp{
-		UniqueCode: excelInfo.UniqueCode,
-		ExcelImage: excelInfo.ExcelImage,
-		ExcelName:  excelInfo.ExcelName,
-		TableInfo:  tableData,
-		Config:     config,
-	}
-	br.Ret = 200
-	br.Success = true
-	br.Msg = "获取成功"
-	br.Data = resp
-}

+ 2 - 0
go.mod

@@ -10,9 +10,11 @@ require (
 	github.com/alibabacloud-go/tea-utils/v2 v2.0.5
 	github.com/aliyun/alibaba-cloud-sdk-go v1.62.696
 	github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
+	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
 	github.com/aws/aws-sdk-go v1.51.2
 	github.com/beego/bee/v2 v2.1.0
 	github.com/beego/beego/v2 v2.1.0
+	github.com/beevik/etree v1.3.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-ldap/ldap v3.0.3+incompatible
 	github.com/go-redis/redis/v8 v8.11.5

+ 7 - 0
go.sum

@@ -61,6 +61,8 @@ github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6
 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.51.2 h1:Ruwgz5aqIXin5Yfcgc+PCzoqW5tEGb9aDL/JWDsre7k=
 github.com/aws/aws-sdk-go v1.51.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
@@ -70,6 +72,8 @@ github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA=
 github.com/beego/beego/v2 v2.1.0/go.mod h1:6h36ISpaxNrrpJ27siTpXBG8d/Icjzsc7pU1bWpp0EE=
 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.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU=
+github.com/beevik/etree v1.3.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=
@@ -256,6 +260,7 @@ github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
 github.com/lib/pq v1.10.5/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=
@@ -349,10 +354,12 @@ 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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+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=

+ 18 - 2
models/data_manage/edb_info.go

@@ -75,6 +75,21 @@ func AddEdbInfo(item *EdbInfo) (lastId int64, err error) {
 	return
 }
 
+type BaseEdbNameItem struct {
+	EdbInfoId   int    `description:"指标id"`
+	EdbInfoType int    `description:"指标类型,0:普通指标,1:预测指标"`
+	SourceName  string `description:"来源名称"`
+	Source      int    `description:"来源id"`
+	EdbCode     string `description:"指标编码"`
+	EdbName     string `description:"指标名称"`
+	Frequency   string `description:"频率"`
+	Unit        string `description:"单位"`
+}
+
+type BaseEdbInfoResp struct {
+	List []*BaseEdbNameItem
+}
+
 type EdbInfoItem struct {
 	EdbInfoId  int    `description:"指标id"`
 	EdbName    string `description:"指标名称"`
@@ -1579,8 +1594,9 @@ type TraceEdbInfoResp struct {
 
 // BeforeAndAfterDateDataResp 前后几期数据
 type BeforeAndAfterDateDataResp struct {
-	List []*EdbDataList `description:"list"`
-	Date string         `description:"实际日期"`
+	List      []*EdbDataList `description:"list"`
+	Date      string         `description:"实际日期"`
+	ShowValue string         `description:"展示值"`
 }
 
 // GetEdbInfoAdminList

+ 3 - 1
models/data_manage/edb_info_calculate.go

@@ -207,6 +207,7 @@ type EdbInfoCalculateBatchSaveReqByEdbLib struct {
 	MoveType         int              `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency    string           `description:"移动频度:天/周/月/季/年"`
 	Calendar         string           `description:"公历/农历"`
+	Data             interface{}      `description:"数据列"`
 	EmptyType        int              `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
 	MaxEmptyType     int              `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	Extra            string           `description:"指标的额外配置"`
@@ -226,7 +227,8 @@ type EdbInfoCalculateBatchEditReqByEdbLib struct {
 	MoveFrequency string `description:"移动频度:天/周/月/季/年"`
 	Calendar      string `description:"公历/农历"`
 	EdbInfoIdArr  []EdbInfoFromTag
-	Extra         string `description:"指标的额外配置"`
+	Extra         string      `description:"指标的额外配置"`
+	Data          interface{} `description:"数据列"`
 }
 
 func GetEdbInfoCalculateMap(edbInfoId, source int) (list []*EdbInfo, err error) {

+ 172 - 0
models/data_manage/excel/excel_classify.go

@@ -0,0 +1,172 @@
+package excel
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// 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"`
+	SysUserRealName   string    `description:"创建人姓名"`
+	Level             int       `description:"层级"`
+	UniqueCode        string    `description:"唯一编码"`
+	Sort              int       `description:"排序字段,越小越靠前,默认值:10"`
+	IsDelete          int       `description:"排序字段,越小越靠前,默认值:10"`
+	CreateTime        time.Time `description:"创建时间"`
+	ModifyTime        time.Time `description:"修改时间"`
+}
+
+// AddExcelClassify 添加excel分类
+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, source int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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
+}
+
+func GetExcelClassifyById(classifyId int) (item *ExcelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_classify WHERE excel_classify_id=? AND is_delete=0 `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func GetChildClassifyById(classifyId int) (items []*ExcelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_classify WHERE parent_id=? AND is_delete=0 `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+func GetExcelClassifyByParentId(parentId, source int) (items []*ExcelClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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
+}
+
+func GetExcelClassifyBySource(source int) (items []*ExcelClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_classify WHERE  source = ? AND is_delete=0 order by sort asc,excel_classify_id asc`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+
+func GetExcelClassifyBySourceOrderByLevel(source int) (items []*ExcelClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_classify WHERE  source = ? AND is_delete=0 order by level asc, sort asc,excel_classify_id asc`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+func GetExcelClassifyAll() (items []*ExcelClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_classify WHERE parent_id<>0 AND is_delete=0 order by sort asc,excel_classify_id asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+type ExcelClassifyItems struct {
+	ExcelClassifyId   int `description:"分类id"`
+	ExcelInfoId       int `description:"表格id"`
+	ExcelClassifyName string
+	ParentId          int
+	Level             int    `description:"层级"`
+	Sort              int    `description:"排序字段,越小越靠前,默认值:10"`
+	UniqueCode        string `description:"唯一编码"`
+	SysUserId         int    `description:"创建人id"`
+	SysUserRealName   string `description:"创建人姓名"`
+	StartDate         string `description:"自定义开始日期"`
+	Children          []*ExcelClassifyItems
+}
+
+func GetExcelClassifyByCondition(condition string, pars []interface{}) (item *ExcelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_classify WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// GetNextExcelClassifyByCondition 获取下一个分类
+func GetNextExcelClassifyByCondition(condition string, pars []interface{}) (item *ExcelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_classify WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " ORDER BY sort asc , create_time ASC LIMIT 1 "
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// GetFirstExcelClassifyByParentId 获取当前父级图表分类下的排序第一条的数据
+func GetFirstExcelClassifyByParentId(parentId int) (item *ExcelClassify, 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 limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// UpdateExcelClassifySortByParentId 根据图表父类id更新排序
+func UpdateExcelClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string, source int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update excel_classify set sort = ` + updateSort + ` WHERE parent_id=? and source=? and sort > ? AND is_delete=0 `
+	if classifyId > 0 {
+		sql += ` or ( excel_classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, source, nowSort).Exec()
+	return
+}
+
+// Update 更新图表分类基础信息
+func (ExcelClassify *ExcelClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(ExcelClassify, cols...)
+	return
+}
+
+// GetExcelClassifyMaxSort 获取图表分类下最大的排序数
+func GetExcelClassifyMaxSort(parentId int, source int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT Max(sort) AS sort FROM excel_classify WHERE parent_id=? AND source = ? AND is_delete=0 `
+	err = o.Raw(sql, parentId, source).QueryRow(&sort)
+	return
+}
+
+type ExcelClassifyView struct {
+	ExcelClassifyId   int    `orm:"column(excel_classify_id);pk"`
+	ExcelClassifyName string `description:"分类名称"`
+	ParentId          int    `description:"父级id"`
+}
+
+func GetExcelClassifyViewById(classifyId int) (item *ExcelClassifyView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_classify WHERE excel_classify_id=? AND is_delete=0 `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// ExcelClassifyItemBySort 自定义比较函数,按年龄从小到大排序
+func ExcelClassifyItemBySort(p1, p2 *ExcelClassifyItems) bool {
+	return p1.Sort < p2.Sort
+}

+ 37 - 0
models/data_manage/excel/excel_draft.go

@@ -0,0 +1,37 @@
+package excel
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ExcelDraft 沙盘推演草稿表
+type ExcelDraft struct {
+	ExcelDraftId int       `orm:"column(excel_draft_id);pk" description:"excel表格草稿记录id"`
+	ExcelId      int       `description:"excel表格id"`
+	Name         string    `description:"excel表格名称"`
+	Content      string    `description:"excel数据"`
+	OpUserId     int       `description:"最近一次编辑操作的用户id"`
+	OpUserName   string    `description:"最近一次编辑的用户名称(冗余字段,避免查表)"`
+	CreateTime   time.Time `description:"创建时间"`
+}
+
+// AddExcelDraft 添加一个新的excel表格草稿
+func AddExcelDraft(excelDraft *ExcelDraft) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 新增草稿
+	id, err := o.Insert(excelDraft)
+	if err != nil {
+		return
+	}
+	excelDraft.ExcelDraftId = int(id)
+	return
+}
+
+// GetLastExcelDraftById 根据沙盘id获取最后一条沙盘草稿详情
+func GetLastExcelDraftById(excelId int) (excelDraft *ExcelDraft, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select * from excel_draft where excel_id = ? order by excel_draft_id desc `
+	err = o.Raw(sql, excelId).QueryRow(&excelDraft)
+	return
+}

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

@@ -0,0 +1,142 @@
+package excel
+
+import (
+	"eta/eta_mobile/utils"
+	"fmt"
+	"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
+// @datetime 2023-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
+}
+
+// GetExcelEdbMappingItemByExcelInfoIdOrKeyword 根据表格ID或关键词获取指标
+func GetExcelEdbMappingItemByExcelInfoIdOrKeyword(excelInfoId int, keyword string) (items []*ExcelEdbMappingItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	cond := `b.excel_info_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, excelInfoId)
+	if keyword != "" {
+		cond += ` AND (a.edb_code LIKE ? OR a.edb_name LIKE ?)`
+		pars = append(pars, keyword, keyword)
+	}
+	sql := fmt.Sprintf(`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
+			%s
+		ORDER BY
+			b.excel_edb_mapping_id ASC`, cond)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}

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

@@ -0,0 +1,527 @@
+package excel
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ExcelInfo excel表格详情表
+type ExcelInfo struct {
+	ExcelInfoId     int       `orm:"column(excel_info_id);pk"`
+	Source          int       `description:"表格来源,1:excel插件的表格,2:自定义表格,3:混合表格,4:自定义分析,默认:1"`
+	ExcelType       int       `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName       string    `description:"表格名称"`
+	UniqueCode      string    `description:"表格唯一编码"`
+	ExcelClassifyId int       `description:"表格分类id"`
+	SysUserId       int       `description:"操作人id"`
+	SysUserRealName string    `description:"操作人真实姓名"`
+	Content         string    `description:"表格内容"`
+	ExcelImage      string    `description:"表格图片"`
+	FileUrl         string    `description:"表格下载地址"`
+	Sort            int       `description:"排序字段,数字越小越排前面"`
+	IsDelete        int       `description:"是否删除,0:未删除,1:已删除"`
+	ModifyTime      time.Time `description:"最近修改日期"`
+	CreateTime      time.Time `description:"创建日期"`
+}
+
+// Update 更新 excel表格基础信息
+func (excelInfo *ExcelInfo) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(excelInfo, cols...)
+	return
+}
+
+type MyExcelInfoList struct {
+	ExcelInfoId     int       `orm:"column(excel_info_id);pk"`
+	Source          int       `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
+	ExcelType       int       `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName       string    `description:"表格名称"`
+	UniqueCode      string    `description:"表格唯一编码"`
+	ExcelClassifyId int       `description:"表格分类id"`
+	SysUserId       int       `description:"操作人id"`
+	SysUserRealName string    `description:"操作人真实姓名"`
+	ExcelImage      string    `description:"表格图片"`
+	FileUrl         string    `description:"表格下载地址"`
+	Sort            int       `description:"排序字段,数字越小越排前面"`
+	ModifyTime      time.Time `description:"最近修改日期"`
+	CreateTime      time.Time `description:"创建日期"`
+}
+
+// AddExcelInfo 新增表格
+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, 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
+}
+
+// GetExcelInfoAll 获取所有表格列表,用于分类展示
+func GetExcelInfoAll() (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,start_date
+            FROM excel_info where is_delete=0 ORDER BY sort asc,create_time ASC `
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetNoContentExcelInfoAll 获取不含content的表格列表 用于分类展示
+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,sort
+            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,excel_info_id 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
+}
+
+// GetExcelInfoById 根据id 获取eta表格详情
+func GetExcelInfoById(excelInfoId int) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE excel_info_id=? AND is_delete=0 `
+	err = o.Raw(sql, excelInfoId).QueryRow(&item)
+	return
+}
+
+// GetExcelInfoByUnicode 编码获取表格
+func GetExcelInfoByUnicode(unicode string) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE unique_code = ? AND is_delete = 0 `
+	err = o.Raw(sql, unicode).QueryRow(&item)
+	return
+}
+
+func GetExcelInfoViewById(excelInfoId int) (item *ExcelInfoView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE excel_info_id=? AND is_delete=0 `
+	err = o.Raw(sql, excelInfoId).QueryRow(&item)
+	return
+}
+
+func GetExcelInfoCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM excel_info WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetExcelInfoByCondition(condition string, pars []interface{}) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// GetNextExcelInfoByCondition 根据条件获取下一个表格
+func GetNextExcelInfoByCondition(condition string, pars []interface{}) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " ORDER BY sort asc , create_time desc LIMIT 1 "
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+// GetNextExcelInfo 根据分类id获取下一个excel表格
+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, source, source).QueryRow(&item)
+	return
+}
+
+// EditExcelInfoImage 修改excel表格的图片
+func EditExcelInfoImage(excelInfoId int, imageUrl string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := ` UPDATE  excel_info SET excel_image=?, modify_time = NOW() WHERE excel_info_id = ? AND is_delete=0 `
+	_, err = o.Raw(sql, imageUrl, excelInfoId).Exec()
+	if err != nil {
+		fmt.Println("EditExcelInfoImage Err:", err.Error())
+		return err
+	}
+
+	return
+}
+
+// GetExcelInfoByUniqueCode 根据unique_code来获取excel表格详情
+func GetExcelInfoByUniqueCode(uniqueCode string) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE unique_code=? AND is_delete=0 `
+	err = o.Raw(sql, uniqueCode).QueryRow(&item)
+	return
+}
+
+// GetFirstExcelInfoByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstExcelInfoByClassifyId(classifyId int) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE excel_classify_id=? AND is_delete=0 order by sort asc,excel_info_id desc limit 1`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// UpdateExcelInfoSortByClassifyId 根据表格id更新排序
+func UpdateExcelInfoSortByClassifyId(classifyId, nowSort, prevExcelInfoId int, updateSort string, source int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update excel_info set sort = ` + updateSort + ` WHERE excel_classify_id=? AND source=? AND is_delete=0 AND ( sort > ? `
+	// todo 前一个兄弟节点后移
+	if prevExcelInfoId > 0 {
+		sql += ` or (excel_info_id < ` + fmt.Sprint(prevExcelInfoId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	sql += `)`
+	_, err = o.Raw(sql, classifyId, source, nowSort).Exec()
+	return
+}
+
+type ExcelInfoView struct {
+	ExcelInfoId       int    `orm:"column(excel_info_id);pk"`
+	ExcelName         string `description:"来源名称"`
+	ExcelClassifyId   int    `description:"表格分类id"`
+	ExcelClassifyName string `description:"表格名称"`
+	SysUserId         int
+	SysUserRealName   string
+	UniqueCode        string `description:"表格唯一编码"`
+	CreateTime        time.Time
+	ModifyTime        time.Time
+	DateType          int    `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"`
+	StartDate         string `description:"自定义开始日期"`
+	EndDate           string `description:"自定义结束日期"`
+	IsSetName         int    `description:"设置名称"`
+	EdbInfoIds        string `description:"指标id"`
+	ExcelType         int    `description:"生成样式:1:曲线图,2:季节性图"`
+	Calendar          string `description:"公历/农历"`
+	SeasonStartDate   string `description:"季节性图开始日期"`
+	SeasonEndDate     string `description:"季节性图开始日期"`
+	ExcelImage        string `description:"表格图片"`
+	Sort              int    `description:"排序字段,数字越小越排前面"`
+	IsAdd             bool   `description:"true:已加入我的图库,false:未加入我的图库"`
+	MyExcelId         int
+	MyExcelClassifyId string `description:"我的表格分类,多个用逗号隔开"`
+	ExcelClassify     []*ExcelClassifyView
+	EdbEndDate        string `description:"指标最新更新日期"`
+	LeftMin           string `description:"表格左侧最小值"`
+	LeftMax           string `description:"表格左侧最大值"`
+	RightMin          string `description:"表格右侧最小值"`
+	RightMax          string `description:"表格右侧最大值"`
+}
+
+// GetExcelInfoByClassifyIdAndName 根据分类id和表格名获取表格信息
+func GetExcelInfoByClassifyIdAndName(classifyId int, excelName string) (item *ExcelInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM excel_info WHERE excel_classify_id = ? and excel_name=? AND is_delete=0 `
+	err = o.Raw(sql, classifyId, excelName).QueryRow(&item)
+	return
+}
+
+// GetNoContentExcelListByCondition 获取没有content的excel表格列表数据
+func GetNoContentExcelListByCondition(condition string, pars []interface{}, startSize, pageSize 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 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	//sql += " ORDER BY sort ASC,chart_info_id DESC LIMIT ?,? "
+	sql += " ORDER BY create_time DESC LIMIT ?,? "
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&item)
+	return
+}
+
+func GetExcelListCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM excel_info WHERE 1=1 AND is_delete=0 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetExcelViewInfoByExcelInfoId 根据excelInfoId 获取ETA表格详情
+func GetExcelViewInfoByExcelInfoId(excelInfoId 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_info_id = ? AND is_delete=0 `
+	err = o.Raw(sql, excelInfoId).QueryRow(&item)
+	return
+}
+
+// GetExcelInfoCountByClassifyId 根据分类id获取名下表格数量
+func GetExcelInfoCountByClassifyId(classifyId int) (total int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT count(1) total FROM excel_info WHERE excel_classify_id = ? AND is_delete=0 `
+	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
+}
+
+// BatchRefreshExcelReq 批量刷新表格请求
+type BatchRefreshExcelReq struct {
+	ExcelCodes      []string `description:"表格编码"`
+	ReportId        int      `description:"报告ID"`
+	ReportChapterId int      `description:"报告章节ID"`
+	Source          string   `description:"来源,枚举值:report、english_report、smart_report"`
+}
+
+// GetExcelMaxSortByClassifyId 获取当前分类下,且排序数最大的excel
+func GetExcelMaxSortByClassifyId(classifyId int, source int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT Max(sort) AS sort FROM excel_info WHERE excel_classify_id=? AND source = ? AND is_delete=0 order by sort desc,excel_info_id desc limit 1`
+	err = o.Raw(sql, classifyId, source).QueryRow(&sort)
+	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_mobile/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:"单位"`
+}

+ 38 - 0
models/data_manage/excel/request/excel_classify.go

@@ -0,0 +1,38 @@
+package request
+
+// AddExcelClassifyReq 添加excel分类请求
+type AddExcelClassifyReq struct {
+	ExcelClassifyName string `description:"分类名称"`
+	ParentId          int    `description:"父级id,第一级传0"`
+	Level             int    `description:"层级,第一级传0,其余传上一级的层级" json:"-"`
+	Source            int    `description:"1:excel插件的表格,2:自定义表格,3:混合表格,默认:1"`
+}
+
+// EditExcelClassifyReq 修改excel分类请求
+type EditExcelClassifyReq struct {
+	ExcelClassifyName string `description:"分类名称"`
+	ExcelClassifyId   int    `description:"分类名称"`
+}
+
+// MoveExcelClassifyReq 移动图表分类请求参数
+type MoveExcelClassifyReq struct {
+	ClassifyId       int `description:"分类id"`
+	ParentClassifyId int `description:"父级分类id"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类id"`
+	NextClassifyId   int `description:"下一个兄弟节点分类id"`
+
+	ExcelInfoId     int `description:"excel表格ID"`
+	PrevExcelInfoId int `description:"上一个excel表格ID"`
+	NextExcelInfoId int `description:"下一个excel表格ID"`
+	Source          int
+}
+
+type DeleteExcelClassifyReq struct {
+	ExcelClassifyId int `description:"分类id"`
+	ExcelInfoId     int `description:"表格id"`
+}
+
+type ExcelClassifyDeleteCheckReq struct {
+	ExcelClassifyId int `description:"分类id"`
+	ExcelInfoId     int `description:"表格id"`
+}

+ 136 - 0
models/data_manage/excel/request/excel_info.go

@@ -0,0 +1,136 @@
+package request
+
+// MoveExcelInfoReq 移动excel表格请求
+type MoveExcelInfoReq struct {
+	ExcelClassifyId int `description:"excel表格分类id"`
+	ExcelInfoId     int `description:"excel表格ID"`
+	PrevExcelInfoId int `description:"上一个excel表格ID"`
+	NextExcelInfoId int `description:"下一个excel表格ID"`
+}
+
+// DeleteExcelInfoReq 删除表格请求
+type DeleteExcelInfoReq struct {
+	ExcelInfoId int `description:"表格ID"`
+}
+
+// AddExcelInfoReq 新增表格请求
+type AddExcelInfoReq 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:"自定义表格的数据内容"`
+}
+
+// EditExcelInfoReq 编辑表格请求
+type EditExcelInfoReq struct {
+	ExcelInfoId     int         `description:"ETA表格ID"`
+	ExcelType       int         `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName       string      `description:"表格名称"`
+	ExcelImage      string      `description:"表格截图"`
+	ExcelClassifyId int         `description:"分类id"`
+	Content         string      `description:"Excel表格内容"`
+	TableData       interface{} `description:"自定义表格的数据内容"`
+}
+
+// SetExcelInfoImageReq 设置excel表格图片请求
+type SetExcelInfoImageReq struct {
+	ExcelInfoId int    `description:"表格ID"`
+	ImageUrl    string `description:"表格图片地址"`
+}
+
+// AddExcelDraftReq 新增表格草稿请求
+type AddExcelDraftReq struct {
+	ExcelInfoId     int    `description:"ETA表格ID"`
+	ExcelName       string `description:"表格名称"`
+	ExcelClassifyId int    `description:"分类id"`
+	Content         string `description:"Excel表格内容"`
+}
+
+// CalculateReq 公式计算
+type CalculateReq struct {
+	CalculateFormula string             `description:"计算公式"`
+	TagMap           map[string]float64 `description:"标签与值的关系"`
+}
+
+// GetOtherEdbDateDataReq 获取其他指标的指定日期的数据
+type GetOtherEdbDateDataReq struct {
+	EdbInfoId int      `description:"指标id"`
+	SortType  string   `description:"如何排序,是正序还是倒序,枚举值:asc 正序,desc 倒序,不传默认倒序"`
+	DateList  []string `description:"日期列表,从小到大"`
+}
+
+// GetFutureDateDataReq 获取未来日期的数据
+type GetFutureDateDataReq struct {
+	EdbInfoIdList []int  `description:"指标id列表,从左至右,从上到下的顺序"`
+	DateType      int    `description:"日期类型,1:期数,2:截止日期"`
+	Num           int    `description:"需要添加的期数"`
+	StartDate     string `description:"开始日期"`
+	EndDate       string `description:"结束日期"`
+}
+
+// GetHistoryDateDataReq 获取历史日期的数据
+type GetHistoryDateDataReq struct {
+	EdbInfoIdList []int  `description:"指标id列表,从左至右,从上到下的顺序"`
+	Num           int    `description:"需要添加的期数"`
+	EndDate       string `description:"结束日期"`
+}
+
+// TableSaveReq 表格保存
+type TableSaveReq struct {
+	ExcelName       string      `description:"表格名称"`
+	ExcelImage      string      `description:"表格截图"`
+	ExcelClassifyId int         `description:"分类id"`
+	TableData       interface{} `description:"表格数据"`
+}
+
+// TableDataReq 自定义表格请求参数
+type TableDataReq struct {
+	EdbInfoIdList []int             `description:"指标id列表,从左至右,从上到下的顺序"`
+	Sort          int               `description:"日期排序,1:倒序,2:正序"`
+	Data          []EdbInfoData     `description:"数据列表"`
+	TextRowData   [][]ManualDataReq `description:"文本列表"`
+}
+
+// EdbInfoData 自定义表格的数据
+type EdbInfoData struct {
+	EdbInfoId    int             `description:"指标ID"`
+	Tag          string          `description:"标签"`
+	EdbName      string          `description:"指标名称"`
+	EdbAliasName string          `description:"指标别名"`
+	Frequency    string          `description:"频度"`
+	Unit         string          `description:"单位"`
+	Data         []ManualDataReq `description:"单元格数据列表"`
+}
+
+// ManualDataReq 自定义表格的单元格数据
+type ManualDataReq struct {
+	DataType            int               `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算,5:预测值"`
+	DataTime            string            `description:"所属日期"`
+	DataTimeType        int               `description:"日期类型,1:实际日期;2:未来日期"`
+	ShowValue           string            `description:"展示值"`
+	Value               string            `description:"实际值(计算公式)"`
+	RelationEdbInfoList []RelationEdbInfo `description:"关联指标(计算公式中关联的指标,用于计算的时候去匹配)"`
+}
+
+// RelationEdbInfo 自定义表格中单元格的关联指标
+type RelationEdbInfo struct {
+	Tag string `description:"指标标签"`
+	Row string `description:"第几行"`
+}
+
+// CopyExcelInfoReq 复制表格请求
+type CopyExcelInfoReq struct {
+	ExcelInfoId     int    `description:"ETA表格ID"`
+	ExcelName       string `description:"表格名称"`
+	ExcelClassifyId int    `description:"分类id"`
+}
+
+// MarkEditExcel 标记编辑表格的请求数据
+type MarkEditExcel struct {
+	ExcelInfoId int `description:"表格id"`
+	Status      int `description:"标记状态,1:编辑中,2:编辑完成"`
+}

+ 166 - 0
models/data_manage/excel/request/mixed_table.go

@@ -0,0 +1,166 @@
+package request
+
+// 单元格的数据类型
+const (
+	DateDT                   = iota + 1 //日期
+	EdbDT                               // 2 指标类型
+	CustomTextDT                        // 3 自定义文本
+	InsertDataDT                        // 4 插值(插入指标值,表格上,自动判断日期和指标的交集位置,插入值)
+	PopInsertDataDT                     // 5 弹框插值(在表格上选择日期,然后空白单元格选择弹框并选择指标,插入该指标与该日期的值)
+	FormulateCalculateDataDT            // 6 公式计算(A+B这种)
+	InsertEdbCalculateDataDT            // 7 插入指标系统计算公式生成的值
+	DateCalculateDataDT                 // 8 日期计算
+)
+
+// 单元格的日期类型类型
+const (
+	CustomDateT = iota //手动输入日期
+	SystemDateT        // 系统日期
+	EdbDateDT          // 导入指标日期(指标库的最新日期)
+
+)
+
+// 单元格的日期类型类型
+const (
+	SystemCurrDateT      = iota + 1 //系统当前日期
+	SystemCalculateDateT            // 系统日期计算后的日期
+	SystemFrequencyDateT            // 导入系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天)
+)
+
+// MixedTableReq 混合表格保存请求参数
+type MixedTableReq struct {
+	CellRelation string                    `description:"单元格关系"`
+	Data         [][]MixedTableCellDataReq `description:"混合表格单元格参数"`
+}
+
+// MixedTableCellDataReq 混合表格单元格参数
+type MixedTableCellDataReq struct {
+	Uid             string      `description:"单元格唯一标识"`
+	DataType        int         `description:"数据类型,1:日期,2:指标,3:自定义文本,4:插值"`
+	DataTime        string      `description:"所属日期"`
+	DataTimeType    int         `description:"日期类型:0:手动输入日期(固定日期);1:导入系统日期;;3:导入指标日期(指标库的最新日期);"`
+	EdbInfoId       int         `description:"指标id"`
+	ShowValue       string      `description:"展示值"`
+	Value           string      `description:"实际值和配置"`
+	Extra           string      `description:"额外参数"`
+	ShowStyle       string      `description:"展示的样式配置"`
+	ShowFormatValue interface{} `description:"样式处理后的值"`
+}
+
+// CellRelationConf
+// @Description: 单元格的关系配置结构体
+type CellRelationConf struct {
+	CellRelation
+	//Type         int          `json:"type" description:"数据类型,跟MixedTableCellDataReq的DataType保持一致"`
+	//Key          string       `json:"key" description:"单元格的唯一标识"`
+	RelationDate CellRelation `json:"relation_date"`
+	RelationEdb  CellRelation `json:"relation_edb"`
+}
+
+// CellRelation
+// @Description: 单元格的关系结构体
+type CellRelation struct {
+	Type int    `json:"type" description:"数据类型,跟MixedTableCellDataReq的DataType保持一致"`
+	Key  string `json:"key" description:"单元格的唯一标识"`
+}
+
+// SystemDateConf
+// @Description: 系统导入日期配置
+type SystemDateConf struct {
+	Source             int    `description:"类型,1:导入系统日期;2:导入系统日期计算后的日期;3:导入系统日期相关的指定频率"`
+	CalculateNum       int    `description:"计算频度的数量"`
+	CalculateFrequency string `description:"计算频度"`
+	Frequency          string `description:"指定频度"`
+	Day                string `description:"指定日期"`
+}
+
+// EdbDateConf
+// @Description: 导入指标日期配置
+type EdbDateConf struct {
+	EdbInfoId int `description:"指标id"`
+	EdbDateChangeConf
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `description:"前移的期数"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+type EdbDateConfDateChange struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
+// MixedDateCalculateReq 混合表格日期计算
+type MixedDateCalculateReq struct {
+	Formula  string                   `description:"计算公式"`
+	DateList []MixDateCalculateTagReq `description:"表格中的单元格"`
+}
+
+// MixDateCalculateConf 混合表格中的日期计算
+type MixDateCalculateConf struct {
+	Formula          string                `description:"计算公式"`
+	RelationCellList []MixDateCalculateTag `description:"表格中的单元格"`
+}
+
+// MixDateCalculateTag 混合表格中的日期计算的关联单元格
+type MixDateCalculateTag struct {
+	Uid string `description:"单元格唯一值"`
+	Tag string `description:"单元格对应标签"`
+}
+
+// MixDateCalculateTagReq 混合表格中的日期计算的关联单元格
+type MixDateCalculateTagReq struct {
+	Date string `description:"日期"`
+	Tag  string `description:"指标对应标签"`
+}
+
+// MixCellShowStyle 混合表格 单元格样式展示计算
+type MixCellShowStyle struct {
+	Pn int    `description:"小数点位数增加或减少,正数表述增加,负数表示减少" json:"pn"`
+	Nt string `description:"变换类型:number 小数点位数改变,percent百分比," json:"nt"`
+}
+
+type DateDataBeforeAfterReq struct {
+	EdbInfoId   int
+	Date        string
+	Num         int
+	MoveForward int `description:"前移的期数"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+// CalculateConf
+// @Description: 计算公式
+type CalculateConf struct {
+	EdbInfoId     int         `description:"指标id"`
+	DataTime      string      `description:"所属日期,这个日期有传递的话,那么就取上下两期+自己的数据;没有传就默认最近5期的数据"`
+	Frequency     string      `description:"需要转换的频度"`
+	Formula       interface{} `description:"计算公式,默认是string,实际上还需要转成其他样式"`
+	Calendar      string      `description:"公历/农历"`
+	MoveType      int         `description:"移动方式:1:领先(默认),2:滞后"`
+	MoveFrequency string      `description:"移动频度"`
+	Source        int         `description:"1:累计值转月;2:累计值转季;3:同比值;4:同差值;5:N数值移动平均数计算;6:环比值;7:环差值;8:升频;9:降频;10:时间移位;11:超季节性;12:年化;13:累计值;14:累计值年初至今;15:指数修匀;16:日均值"`
+
+	EdbDateChangeConf
+}
+
+// BaseCalculateConf
+// @Description: 基础计算公式(A+B)
+type BaseCalculateConf struct {
+	Formula             string         `description:"计算公式,默认是string,实际上还需要转成其他样式"`
+	RelationEdbInfoList []RelationCell `description:"关联单元格(计算公式中关联的单元格,用于计算的时候去匹配)"`
+}
+
+// RelationCell
+// @Description: 关联单元格的信息
+type RelationCell struct {
+	Tag string `description:"指标标签"`
+	Row string `description:"第几行"`
+	Key string `json:"key" description:"单元格的唯一标识"`
+}

+ 14 - 0
models/data_manage/excel/response/excel_classify.go

@@ -0,0 +1,14 @@
+package response
+
+import (
+	"eta/eta_mobile/models/data_manage/excel"
+)
+
+type ExcelClassifyListResp struct {
+	AllNodes []*excel.ExcelClassifyItems
+}
+
+type ExcelClassifyDeleteCheckResp struct {
+	DeleteStatus int    `description:"检测状态:0:默认值,如果为0,继续走其他校验,1:该分类下关联图表不可删除,2:确认删除当前目录及包含的子目录吗"`
+	TipsMsg      string `description:"提示信息"`
+}

+ 89 - 0
models/data_manage/excel/response/excel_info.go

@@ -0,0 +1,89 @@
+package response
+
+import (
+	excel2 "eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/services/excel"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+// AddExcelInfoResp 添加excel表格的返回
+type AddExcelInfoResp struct {
+	ExcelInfoId int    `description:"表格id"`
+	UniqueCode  string `description:"表格唯一编码"`
+}
+
+// ExcelListResp 表格列表返回数据
+type ExcelListResp struct {
+	Paging *paging.PagingItem
+	List   []*excel2.MyExcelInfoList
+}
+
+// ExcelTableDetailResp  excel表格详情
+type ExcelTableDetailResp struct {
+	UniqueCode string `description:"表格唯一code"`
+	ExcelImage string `description:"表格截图"`
+	ExcelName  string `description:"表格名称"`
+	TableInfo  excel.TableData
+	Config     ExcelTableDetailConfigResp
+}
+
+// ExcelTableDetailConfigResp
+// @Description: Excel表格的配置信息
+type ExcelTableDetailConfigResp struct {
+	FontSize int
+}
+
+// TableCellResp 单元格
+type TableCellResp struct {
+	DataType  int    `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算"`
+	DataTime  string `description:"所属日期"`
+	ShowValue string `description:"展示的值"`
+	Value     string `description:"实际值(计算公式)"`
+}
+
+type TableDataItem struct {
+	EdbInfoId int                     `description:"指标id"`
+	Data      []request.ManualDataReq `description:"数据列表"`
+}
+
+// TableDetailResp  excel表格详情
+type TableDetailResp struct {
+	ExcelInfo excel2.ExcelInfo     `description:"表格基础信息"`
+	TableData request.TableDataReq `description:"表格内容"`
+}
+
+// ExcelInfoDetail excel表格详情(前端使用)
+type ExcelInfoDetail struct {
+	ExcelInfoId     int                   `orm:"column(excel_info_id);pk"`
+	Source          int                   `description:"表格来源,1:excel插件的表格,2:自定义表格,默认:1"`
+	ExcelType       int                   `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelName       string                `description:"表格名称"`
+	UniqueCode      string                `description:"表格唯一编码"`
+	ExcelClassifyId int                   `description:"表格分类id"`
+	SysUserId       int                   `description:"操作人id"`
+	SysUserRealName string                `description:"操作人真实姓名"`
+	Content         string                `description:"表格内容"`
+	ExcelImage      string                `description:"表格图片"`
+	FileUrl         string                `description:"表格下载地址"`
+	Sort            int                   `description:"排序字段,数字越小越排前面"`
+	IsDelete        int                   `description:"是否删除,0:未删除,1:已删除"`
+	ModifyTime      time.Time             `description:"最近修改日期"`
+	CreateTime      time.Time             `description:"创建日期"`
+	TableData       interface{}           `description:"表格内容"`
+	Button          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:"是否可删除"`
+	OpEdbButton      bool `description:"是否可生成指标"`
+	RefreshEdbButton bool `description:"是否可刷新指标"`
+}

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

@@ -0,0 +1,33 @@
+package response
+
+import (
+	"eta/eta_mobile/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:"编辑人"`
+}

+ 351 - 18
routers/commentsRouter.go

@@ -322,6 +322,357 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "AddEdb",
+            Router: `/edb/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "EditEdb",
+            Router: `/edb/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "EdbList",
+            Router: `/edb/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "EdbRefresh",
+            Router: `/edb/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "BaseExcelDetail",
+            Router: `/excel/base`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "ExcelDataList",
+            Router: `/excel/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "ExcelByName",
+            Router: `/excel_by_name`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:CustomAnalysisController"],
+        beego.ControllerComments{
+            Method: "Save",
+            Router: `/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "AddExcelClassify",
+            Router: `/excel_classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteExcelClassify",
+            Router: `/excel_classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteExcelClassifyCheck",
+            Router: `/excel_classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "EditExcelClassify",
+            Router: `/excel_classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "ExcelClassifyItems",
+            Router: `/excel_classify/items`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/excel_classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelClassifyController"],
+        beego.ControllerComments{
+            Method: "ExcelClassifyMove",
+            Router: `/excel_classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/excel_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetBaseEdbInfo",
+            Router: `/excel_info/base_edb_info`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Copy",
+            Router: `/excel_info/copy`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/excel_info/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/excel_info/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "AddDraft",
+            Router: `/excel_info/draft/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/excel_info/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetSystemDate",
+            Router: `/excel_info/get_system_date`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/excel_info/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "MarkEditStatus",
+            Router: `/excel_info/mark`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "CalculateData",
+            Router: `/excel_info/mixed/calculate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetMixDateCalculate",
+            Router: `/excel_info/mixed/date_calculate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/excel_info/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "BatchRefresh",
+            Router: `/excel_info/table/batch_refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetBatchChartRefreshResult",
+            Router: `/excel_info/table/batch_refresh/result`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Calculate",
+            Router: `/excel_info/table/calculate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Download",
+            Router: `/excel_info/table/download`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetFirstEdbData",
+            Router: `/excel_info/table/first_edb_data_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetFutureDateData",
+            Router: `/excel_info/table/future_date_list`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetHistoryDateData",
+            Router: `/excel_info/table/history_date_list`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetOtherEdbData",
+            Router: `/excel_info/table/other_edb_data_list`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/excel_info/table/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetExcelTableData",
+            Router: `/excel_info/table_data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/future_good:FutureGoodChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/future_good:FutureGoodChartClassifyController"],
         beego.ControllerComments{
             Method: "ChartClassifyList",
@@ -1051,24 +1402,6 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ExcelInfoController"],
-        beego.ControllerComments{
-            Method: "List",
-            Router: `/excel_info/list`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:ExcelInfoController"],
-        beego.ControllerComments{
-            Method: "GetExcelTableData",
-            Router: `/excel_info/table_data`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(),
-            Filters: nil,
-            Params: nil})
-
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:MyChartController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage:MyChartController"],
         beego.ControllerComments{
             Method: "MyChartAdd",

+ 3 - 1
routers/router.go

@@ -12,6 +12,7 @@ import (
 	"eta/eta_mobile/controllers/data_manage"
 	"eta/eta_mobile/controllers/data_manage/correlation"
 	"eta/eta_mobile/controllers/data_manage/cross_variety"
+	"eta/eta_mobile/controllers/data_manage/excel"
 	"eta/eta_mobile/controllers/data_manage/future_good"
 	"eta/eta_mobile/controllers/data_manage/line_equation"
 	"eta/eta_mobile/controllers/data_manage/line_feature"
@@ -74,8 +75,9 @@ func init() {
 				&data_manage.EdbClassifyController{},
 				&data_manage.ChartClassifyController{},
 				&data_manage.ChartInfoController{},
-				&data_manage.ExcelInfoController{},
 				&data_manage.PredictEdbInfoController{},
+				&excel.ExcelClassifyController{},
+				&excel.ExcelInfoController{},
 			),
 		),
 		web.NSNamespace("/resource",

+ 51 - 1
services/data/base_edb_lib.go

@@ -96,7 +96,7 @@ type AddPredictEdbDataResponse struct {
 }
 
 // RefreshEdbData 刷新指标数据
-func RefreshEdbData(edbInfoId, source ,subSource int, edbCode, startDate string) (resp *models.BaseResponse, err error) {
+func RefreshEdbData(edbInfoId, source, subSource int, edbCode, startDate string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["EdbInfoId"] = edbInfoId
@@ -347,3 +347,53 @@ func AddEdbDataThsDs(source int, stockCode, edbCode string) (resp *models.BaseRe
 	resp, err = postRefreshEdbData(param, urlStr)
 	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
+}
+
+// PredictCalculateComputeCorrelation 拟合残差计算相关性的值(预测指标)
+func PredictCalculateComputeCorrelation(edbInfoCalculateBatchSaveReqStr string) (resp *CalculateComputeCorrelationResp, err error) {
+	_, resultByte, err := postAddEdbData(edbInfoCalculateBatchSaveReqStr, "predict_calculate/compute_correlation")
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// BaseCalculateResp 拟合残差计算相关性的值返回
+type BaseCalculateResp struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        BaseCalculateDataResp
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+// BaseCalculateDataResp
+// @Description: 基础计算的返回结果
+type BaseCalculateDataResp struct {
+	DataMap  map[string]float64
+	DateList []string
+}
+
+// BaseCalculate 基础计算
+func BaseCalculate(param string) (resp *BaseCalculateResp, err error) {
+	urlStr := "calculate/base"
+	_, resultByte, err := postAddEdbData(param, urlStr)
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}

+ 2 - 2
services/data/chart_info.go

@@ -305,7 +305,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		if err != nil {
 			return
 		}
-		
+
 		if v.IsConvert == 1 {
 			switch v.ConvertType {
 			case 1:
@@ -1433,7 +1433,7 @@ func ChartInfoRefreshV2(chartInfoId int) (err error) {
 	}
 
 	// 批量刷新
-	err, _ = EdbInfoRefreshAllFromBaseV3(edbIdList, false, true)
+	err, _ = EdbInfoRefreshAllFromBaseV3(edbIdList, false, false, false)
 	if err != nil {
 		return
 	}

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

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

+ 213 - 6
services/data/edb_info.go

@@ -15,7 +15,7 @@ import (
 )
 
 // EdbInfoRefreshAllFromBaseV2 全部刷新指标(切换到edb_lib服务)
-func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll bool) (err error, isAsync bool) {
+func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll, isRefreshTop bool) (err error, isAsync bool) {
 	var errmsg string
 	defer func() {
 		if err != nil {
@@ -25,7 +25,7 @@ func EdbInfoRefreshAllFromBaseV2(edbInfoId int, refreshAll bool) (err error, isA
 		}
 	}()
 
-	err, isAsync = EdbInfoRefreshAllFromBaseV3([]int{edbInfoId}, refreshAll, false)
+	err, isAsync = EdbInfoRefreshAllFromBaseV3([]int{edbInfoId}, refreshAll, false, isRefreshTop)
 	return
 	//// 获取关联的基础指标
 	//newBaseEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr, err, errmsg := getRefreshEdbInfoList(edbInfoId)
@@ -295,13 +295,14 @@ func EdbInfoRefreshAllFromBaseV3Bak(edbInfoIdList []int, refreshAll, isSync bool
 //
 //	@Description: 全部刷新指标(切换到edb_lib服务)
 //	@author: Roc
-//	@datetime2023-10-23 09:57:55
+//	@datetime 2023-10-23 09:57:55
 //	@param edbInfoIdList []int
 //	@param refreshAll bool
 //	@param isSync bool 是否同步执行
+//	@param isRefreshTop bool 是否刷新上面的指标
 //	@return err error
 //	@return isAsync bool 是否异步刷新
-func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (err error, isAsync bool) {
+func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync, isRefreshTop bool) (err error, isAsync bool) {
 	var errmsg string
 	defer func() {
 		if err != nil {
@@ -310,8 +311,21 @@ func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync bool) (
 		}
 	}()
 
-	// 获取需要刷新的指标列表
-	newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr := getEdbInfoIdList(edbInfoIdList)
+	var newBaseEdbInfoArr, newBasePredictEdbInfoArr []*data_manage.EdbInfo
+	var newCalculateMap, newPredictCalculateMap map[int]*data_manage.EdbInfo
+	var calculateArr, predictCalculateArr []int
+
+	// 获取关联指标
+	if isRefreshTop {
+		// 获取所有关联的指标,上下所有的指标
+		newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr, err, errmsg = getRefreshEdbInfoListByIds(edbInfoIdList)
+		if err != nil {
+			return
+		}
+	} else {
+		// 获取溯源的指标
+		newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr = getEdbInfoIdList(edbInfoIdList)
+	}
 
 	// 需要刷新的指标数量
 	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
@@ -1680,3 +1694,196 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	AddOrEditEdbInfoToEs(int(edbInfoId))
 	return
 }
+
+// BatchRefreshEdbByEdbIds 批量刷新指标
+func BatchRefreshEdbByEdbIds(edbIdList []int, redisKey string, refreshKeys []string) (syncing bool, err error) {
+	if len(edbIdList) <= 0 {
+		return
+	}
+
+	// 设置刷新缓存
+	if redisKey != `` {
+		// 设置最多10分钟缓存
+		utils.Rc.SetNX(redisKey, 1, time.Minute*10)
+	}
+
+	// 获取需要刷新的指标列表
+	newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr := getEdbInfoIdList(edbIdList)
+
+	// 需要刷新的指标数量
+	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
+
+	// 关联指标过多的时候, 异步刷新
+	if totalEdbInfo > 20 {
+		syncing = true
+
+		go func() {
+			defer func() {
+				if err != nil {
+					alarm_msg.SendAlarmMsg("BatchRefreshEdbByEdbIds, ErrMsg: "+err.Error(), 3)
+				}
+			}()
+
+			err = edbInfoRefreshAll(false, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
+
+			if redisKey != `` {
+				_ = utils.Rc.Delete(redisKey)
+			}
+			if len(refreshKeys) > 0 {
+				for _, v := range refreshKeys {
+					_ = utils.Rc.Delete(v)
+				}
+			}
+		}()
+		return
+	}
+
+	// 同步刷新
+	err = edbInfoRefreshAll(false, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
+
+	if redisKey != `` {
+		_ = utils.Rc.Delete(redisKey)
+	}
+	if len(refreshKeys) > 0 {
+		for _, v := range refreshKeys {
+			_ = utils.Rc.Delete(v)
+		}
+	}
+	return
+}
+
+// EdbInfoSmmApiAdd 添加指标到指标库
+func EdbInfoSmmApiAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	//判断指标名称是否存在
+	var condition string
+	var pars []interface{}
+
+	condition += " AND edb_name=? "
+	pars = append(pars, item.EdbName)
+
+	count, err := data_manage.GetEdbInfoCountByCondition(condition, pars)
+	if err != nil {
+		errMsg = "判断指标名称是否存在失败"
+		err = errors.New("判断指标名称是否存在失败,Err:" + err.Error())
+		return
+	}
+
+	if count > 0 {
+		errMsg = "指标名称已存在,请重新填写"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	source := item.Source
+
+	edbInfo = new(data_manage.EdbInfo)
+	edbInfo.Source = source
+	sourceNameMap := map[int]string{
+		utils.DATA_SOURCE_THS:                 "同花顺",
+		utils.DATA_SOURCE_WIND:                "wind",
+		utils.DATA_SOURCE_PB:                  "彭博",
+		utils.DATA_SOURCE_PB_FINANCE:          "彭博财务",
+		utils.DATA_SOURCE_MANUAL:              "手工数据",
+		utils.DATA_SOURCE_LZ:                  "隆众",
+		utils.DATA_SOURCE_YS:                  "SMM",
+		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_ZZ:                  "郑商所",
+		utils.DATA_SOURCE_DL:                  "大商所",
+		utils.DATA_SOURCE_SH:                  "上期所",
+		utils.DATA_SOURCE_CFFEX:               "中金所",
+		utils.DATA_SOURCE_SHFE:                "上期能源",
+		utils.DATA_SOURCE_GIE:                 "欧洲天然气",
+		utils.DATA_SOURCE_LT:                  "路透",
+		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
+		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
+		utils.DATA_SOURCE_COM_TRADE:           "UN",
+		utils.DATA_SOURCE_SCI:                 "SCI",
+		utils.DATA_SOURCE_BAIINFO:             "BAIINFO",
+		utils.DATA_SOURCE_STOCK_PLANT:         "存量装置",
+		utils.DATA_SOURCE_NATIONAL_STATISTICS: "国家统计局",
+		utils.DATA_SOURCE_FUBAO:               "富宝数据",
+	}
+
+	sourceName, ok := sourceNameMap[source]
+	if !ok {
+		edbSource := data_manage.EdbSourceIdMap[source]
+		if edbSource != nil {
+			sourceName = edbSource.SourceName
+		}
+		if sourceName == "" {
+			errMsg = "指标来源异常"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+	edbInfo.SourceName = sourceName
+
+	edbType := 1 //基础指标
+	if source == utils.DATA_SOURCE_STOCK_PLANT {
+		edbType = 2 //计算指标
+	}
+	//从缓存中获取
+	serverUrl := ``
+	if edbInfo.Source == utils.DATA_SOURCE_WIND {
+		windCacheKey := utils.CACHE_WIND_URL + ":" + item.EdbCode
+		serverUrl, _ = utils.Rc.RedisString(windCacheKey)
+		if serverUrl == `` {
+			if len(utils.Hz_Data_WIND_Url_List) >= 1 {
+				serverUrl = utils.Hz_Data_WIND_Url_List[len(utils.Hz_Data_WIND_Url_List)-1] //默认是最后一个服务器地址
+			}
+		}
+	}
+	//获取该层级下最大的排序数
+	maxSort, err := GetEdbClassifyMaxSort(item.ClassifyId, 0)
+	if err != nil {
+		errMsg = "获取失败"
+		err = errors.New("查询排序信息失败,Err:" + err.Error())
+		return
+	}
+
+	edbInfo.EdbCode = item.EdbCode
+	edbInfo.EdbName = item.EdbName
+	edbInfo.EdbNameSource = item.EdbName
+	edbInfo.Frequency = item.Frequency
+	edbInfo.Unit = item.Unit
+	edbInfo.ClassifyId = item.ClassifyId
+	edbInfo.SysUserId = item.SysUserId
+	edbInfo.SysUserRealName = item.SysUserRealName
+	edbInfo.CreateTime = time.Now()
+	edbInfo.ModifyTime = time.Now()
+	edbInfo.ServerUrl = serverUrl
+	edbInfo.Sort = maxSort + 1
+	edbInfo.DataDateType = `交易日`
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	edbInfo.UniqueCode = utils.MD5(utils.DATA_PREFIX + "_" + timestamp)
+	edbInfo.EdbType = edbType
+	edbInfo.SubSource = item.SubSource
+	edbInfo.SubSourceName = ""
+	edbInfo.IndicatorCode = item.IndicatorCode
+	edbInfo.StockCode = item.StockCode
+	edbInfoId, err := data_manage.AddEdbInfo(edbInfo)
+	if err != nil {
+		errMsg = "保存失败"
+		err = errors.New("保存失败,Err:" + err.Error())
+		return
+	}
+	edbInfo.EdbInfoId = int(edbInfoId)
+	//保存数据
+	err = data_manage.ModifyEdbInfoWindWsdDataStatus(source, item.SubSource, edbInfoId, item.EdbCode)
+	if err != nil {
+		errMsg = "保存失败"
+		err = errors.New("修改数据对应指标ID失败,Err:" + err.Error())
+		return
+	}
+	maxAndMinItem, _ := data_manage.GetEdbInfoWsdMaxAndMinInfo(source, item.SubSource, item.EdbCode)
+	if maxAndMinItem != nil {
+		err = data_manage.ModifyEdbInfoMaxAndMinInfo(int(edbInfoId), maxAndMinItem)
+	}
+	//添加es
+	AddOrEditEdbInfoToEs(int(edbInfoId))
+	return
+}

+ 5 - 0
services/data/edb_info_calculate.go

@@ -731,3 +731,8 @@ 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)
+}

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

@@ -0,0 +1,426 @@
+package excel
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/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        float64              `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"`
+}

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

@@ -0,0 +1,623 @@
+package excel
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/services/data"
+	excelServices "eta/eta_mobile/services/excel"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/araddon/dateparse"
+	"github.com/shopspring/decimal"
+	"github.com/tealeg/xlsx"
+	"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
+}
+
+// 数据集
+type dataStruct struct {
+	Value float64
+	Ok    bool
+}
+
+// HandleEdbSequenceVal 处理日期集和数据集(获取可用的日期、数据集)
+func HandleEdbSequenceVal(dateSequenceVal, dataSequenceVal []string) (newDateList []string, newDataList []float64, err error, errMsg string) {
+	newDateList = make([]string, 0)
+	newDataList = make([]float64, 0)
+
+	dataList := make([]dataStruct, 0)
+	{
+		for _, v := range dataSequenceVal {
+			// 如果没有数据集,那么就过滤
+			if v == `` {
+				// 如果开始插入数据了,那么就需要插入不存在值
+				dataList = append(dataList, dataStruct{
+					Value: 0,
+					Ok:    false,
+				})
+				continue
+			}
+
+			v = strings.Replace(v, ",", "", -1)
+			v = strings.Replace(v, ",", "", -1)
+			// 过滤空格
+			v = strings.Replace(v, " ", "", -1)
+
+			var tmpVal float64
+			if strings.Contains(v, "%") {
+				// 百分比的数
+				isPercentage, percentageValue := utils.IsPercentage(v)
+				if !isPercentage {
+					dataList = append(dataList, dataStruct{
+						Value: 0,
+						Ok:    false,
+					})
+					continue
+				}
+				tmpValDec, tmpErr := decimal.NewFromString(percentageValue)
+				if tmpErr != nil {
+					dataList = append(dataList, dataStruct{
+						Value: 0,
+						Ok:    false,
+					})
+					continue
+				}
+				tmpVal, _ = tmpValDec.Div(decimal.NewFromFloat(100)).Float64()
+			} else {
+				tmpValDec, tmpErr := decimal.NewFromString(v)
+				if tmpErr != nil {
+					dataList = append(dataList, dataStruct{
+						Value: 0,
+						Ok:    false,
+					})
+					continue
+				}
+				tmpVal, _ = tmpValDec.Float64()
+			}
+			dataList = append(dataList, dataStruct{
+				Value: tmpVal,
+				Ok:    true,
+			})
+		}
+	}
+
+	// 日期集
+	dateList := make([]string, 0)
+	{
+		for _, v := range dateSequenceVal {
+			// 如果没有数据集,那么就过滤
+			if v == `` {
+				// 如果开始插入数据了,那么就需要插入不存在值
+				dateList = append(dateList, "")
+				continue
+			}
+
+			// 过滤空格
+			v = strings.Replace(v, " ", "", -1)
+			t1, tmpErr := dateparse.ParseAny(v)
+			if tmpErr != nil {
+				dateList = append(dateList, "")
+				continue
+			}
+
+			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]
+		tmpData := dataList[i]
+
+		// 日期为空、数据为空
+		if !tmpData.Ok || date == `` {
+			continue
+		}
+
+		newDateList = append(newDateList, date)
+		newDataList = append(newDataList, tmpData.Value)
+	}
+
+	return
+}
+
+// ResetCustomAnalysisData 数据重置的结构体
+type ResetCustomAnalysisData struct {
+	EdbInfoId int
+	DateList  []string
+	DataList  []float64
+}
+
+// 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)
+
+	edbInfoIdList := make([]int, 0)
+
+	for _, v := range list {
+		edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+
+		// 获取对应的日期和数据列表
+		relDateList, relDataList, tmpErr, tmpErrMsg := getDateAndDataList(v, xlsxFile)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = tmpErrMsg
+			return
+		}
+		//fmt.Println(v)
+		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
+		}
+	}
+
+	if len(edbInfoIdList) > 0 {
+		err, _ = data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, true, true)
+	}
+
+	//xlsxFile.Sheet[]
+
+	return
+}
+
+// getDateAndDataList
+// @Description: 获取待刷新的日期和数据
+// @author: Roc
+// @datetime 2023-12-21 15:21:14
+// @param excelEdbMappingItem *excel.ExcelEdbMappingItem
+// @param xlsxFile *xlsx.File
+// @return newDateList []string
+// @return newDataList []float64
+// @return err error
+// @return errMsg string
+func getDateAndDataList(excelEdbMappingItem *excel.ExcelEdbMappingItem, xlsxFile *xlsx.File) (newDateList []string, newDataList []float64, err error, errMsg string) {
+	var dateList, dataList []string
+
+	// 日期序列
+	{
+		sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(excelEdbMappingItem.DateSequenceStr)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 查找sheet页
+		sheetInfo, ok := xlsxFile.Sheet[sheetName]
+		if !ok {
+			errMsg = "找不到" + sheetName
+			err = errors.New(errMsg)
+			return
+		}
+
+		startNum = startNum - 1
+		endNum = endNum - 1
+		// 选择行的数据
+		if isRow {
+			// 因为是选择一行的数据,所以开始行和结束行时一样的
+			//endNum = startNum - 1
+
+			// 开始列名、结束列
+			var startColumn, endColumn int
+			if isAll {
+				// 结束列(其实也就是整列的个数)
+				endColumn = len(sheetInfo.Cols) - 1
+			} else {
+				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
+			}
+
+			// 最大列数,如果设置的超过了最大列数,那么结束列就是最大列数
+			maxCol := len(sheetInfo.Cols)
+			if endColumn > maxCol {
+				endColumn = maxCol - 1
+			}
+
+			// 长度固定,避免一直申请内存空间
+			dateList = make([]string, endColumn-startColumn+1)
+
+			i := 0
+			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)
+				dateList[i] = currCell.Value
+				i++
+			}
+
+		} else if isColumn { // 选择列的数据
+			if isAll {
+				// 选择一整列的话,结束行得根据实际情况调整(其实也就是整个sheet有多少行)
+				endNum = len(sheetInfo.Rows) - 1
+			}
+
+			startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+			if tmpErr != nil {
+				errMsg = "列名异常:" + startColumnName
+				err = errors.New(errMsg)
+				return
+			}
+			startColumn = startColumn - 1
+
+			// 最大行数,如果设置的超过了最大行数,那么结束行就是最大行数
+			maxRow := len(sheetInfo.Rows)
+			if endNum > maxRow {
+				endNum = maxRow - 1
+			}
+			// 长度固定,避免一直申请内存空间
+			dateList = make([]string, endNum-startNum+1)
+			i := 0
+			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)
+				dateList[i] = currCell.Value
+				i++
+			}
+		}
+
+	}
+
+	// 数据序列
+	{
+		sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(excelEdbMappingItem.DataSequenceStr)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 查找sheet页
+		sheetInfo, ok := xlsxFile.Sheet[sheetName]
+		if !ok {
+			errMsg = "找不到" + sheetName
+			err = errors.New(errMsg)
+			return
+		}
+
+		startNum = startNum - 1
+		endNum = endNum - 1
+		// 选择行的数据
+		if isRow {
+			// 开始列名、结束列
+			var startColumn, endColumn int
+			if isAll {
+				// 结束列(其实也就是整列的个数)
+				endColumn = len(sheetInfo.Cols) - 1
+			} else {
+
+				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
+			}
+
+			// 最大列数,如果设置的超过了最大列数,那么结束列就是最大列数
+			maxCol := len(sheetInfo.Cols)
+			if endColumn > maxCol {
+				endColumn = maxCol - 1
+			}
+			// 长度固定,避免一直申请内存空间
+			dataList = make([]string, endColumn-startColumn+1)
+			i := 0
+			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)
+				dataList[i] = currCell.Value
+				i++
+			}
+
+		} else if isColumn { // 选择列的数据
+			if isAll {
+				// 选择一整列的话,结束行得根据实际情况调整(其实也就是整个sheet有多少行)
+				endNum = len(sheetInfo.Rows) - 1
+			}
+
+			startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+			if tmpErr != nil {
+				errMsg = "列名异常:" + startColumnName
+				err = errors.New(errMsg)
+				return
+			}
+			startColumn = startColumn - 1
+
+			// 最大行数,如果设置的超过了最大行数,那么结束行就是最大行数
+			maxRow := len(sheetInfo.Rows)
+			if endNum > maxRow {
+				endNum = maxRow - 1
+			}
+
+			// 长度固定,避免一直申请内存空间
+			dataList = make([]string, endNum-startNum+1)
+			i := 0
+			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)
+				dataList[i] = currCell.Value
+				i++
+			}
+		}
+
+	}
+
+	//fmt.Println(dateList, dataList)
+	//fmt.Println("日期序列结束")
+
+	// 将excel中的日期、数据系列处理
+	newDateList, newDataList, err, errMsg = HandleEdbSequenceVal(dateList, dataList)
+
+	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
+}

+ 475 - 0
services/data/excel/excel_classify.go

@@ -0,0 +1,475 @@
+package excel
+
+import (
+	"errors"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/utils"
+	"time"
+)
+
+func GetExcelClassifyMaxSort(parentId int, source int) (maxSort int, err error) {
+	//获取该层级下最大的排序数
+	maxSort, err = excel.GetExcelClassifyMaxSort(parentId, source)
+	if err != nil {
+		return
+	}
+	edbMaxSort, err := excel.GetExcelMaxSortByClassifyId(parentId, source)
+	if err != nil {
+		return
+	}
+	if maxSort < edbMaxSort {
+		maxSort = edbMaxSort
+	}
+	return
+}
+
+// MoveExcelClassify 移动指标分类
+func MoveExcelClassify(req request.MoveExcelClassifyReq) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	parentClassifyId := req.ParentClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	excelInfoId := req.ExcelInfoId
+	prevExcelInfoId := req.PrevExcelInfoId
+	nextExcelInfoId := req.NextExcelInfoId
+	source := req.Source
+	//首先确定移动的对象是分类还是指标
+	//判断上一个节点是分类还是指标
+	//判断下一个节点是分类还是指标
+	//同时更新分类目录下的分类sort和指标sort
+	//更新当前移动的分类或者指标sort
+
+	var parentExcelClassifyInfo *excel.ExcelClassify
+	if parentClassifyId > 0 {
+		parentExcelClassifyInfo, err = excel.GetExcelClassifyById(parentClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		excelClassifyInfo *excel.ExcelClassify
+		prevClassify      *excel.ExcelClassify
+		nextClassify      *excel.ExcelClassify
+
+		excelInfo     *excel.ExcelInfo
+		prevExcelInfo *excel.ExcelInfo
+		nextExcelInfo *excel.ExcelInfo
+		prevSort      int
+		nextSort      int
+	)
+
+	// 移动对象为分类, 判断权限
+	if excelInfoId == 0 {
+		excelClassifyInfo, err = excel.GetExcelClassifyById(classifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前分类不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+
+	} else {
+		excelInfo, err = excel.GetExcelInfoById(excelInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前表格不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId == 0 {
+			errMsg = "移动失败,表格必须挂在分类下"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	if prevClassifyId > 0 {
+		prevClassify, err = excel.GetExcelClassifyById(prevClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	} else if prevExcelInfoId > 0 {
+		prevExcelInfo, err = excel.GetExcelInfoById(prevExcelInfoId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevExcelInfo.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = excel.GetExcelClassifyById(nextClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	} else if nextExcelInfoId > 0 {
+		//下一个兄弟节点
+		nextExcelInfo, err = excel.GetExcelInfoById(nextExcelInfoId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextExcelInfo.Sort
+	}
+
+	err, errMsg = moveExcelClassify(parentExcelClassifyInfo, excelClassifyInfo, prevClassify, nextClassify, excelInfo, prevExcelInfo, nextExcelInfo, parentClassifyId, prevSort, nextSort, source)
+	return
+}
+
+// moveExcelClassify 移动表格分类
+func moveExcelClassify(parentExcelClassifyInfo, excelClassifyInfo, prevClassify, nextClassify *excel.ExcelClassify, excelInfo, prevExcelInfo, nextExcelInfo *excel.ExcelInfo, parentClassifyId int, prevSort, nextSort int, source int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+
+	// 移动对象为分类, 判断分类是否存在
+	if excelClassifyInfo != nil {
+		oldParentId := excelClassifyInfo.ParentId
+		/*oldLevel := excelClassifyInfo.Level
+		var classifyIds []int*/
+		if oldParentId != parentClassifyId {
+			//todo 更新子分类对应的level
+			/*childList, e, m := GetChildClassifyByClassifyId(excelClassifyInfo.ClassifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子分类失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.ClassifyId == excelClassifyInfo.ClassifyId {
+						continue
+					}
+					classifyIds = append(classifyIds, v.ClassifyId)
+				}
+			}*/
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if excelClassifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+			if excelClassifyInfo.Level != parentExcelClassifyInfo.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			excelClassifyInfo.ParentId = parentExcelClassifyInfo.ExcelClassifyId
+			excelClassifyInfo.Level = parentExcelClassifyInfo.Level + 1
+			excelClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+		} else if excelClassifyInfo.ParentId != parentClassifyId && parentClassifyId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == excelClassifyInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, prevClassify.ExcelClassifyId, prevClassify.Sort, updateSortStr, source)
+					} else {
+						_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr, source)
+					}
+
+					//变更表格
+					if prevExcelInfo != nil {
+						//变更兄弟节点的排序
+						_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, prevExcelInfo.ExcelInfoId, updateSortStr, source)
+					} else {
+						_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr, source)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更分类
+						if prevClassify != nil {
+							_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, prevClassify.ExcelClassifyId, prevSort, updateSortStr, source)
+						} else {
+							_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr, source)
+						}
+
+						//变更表格
+						if prevExcelInfo != nil {
+							//变更兄弟节点的排序
+							_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, prevExcelInfo.ExcelInfoId, updateSortStr, source)
+						} else {
+							_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr, source)
+						}
+
+					}
+				}
+			}
+
+			excelClassifyInfo.Sort = prevSort + 1
+			excelClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevExcelInfo == nil && nextExcelInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetExcelClassifyMaxSort(parentClassifyId, source)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			excelClassifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			excelClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := data_manage.GetFirstEdbClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, firstClassify.ClassifyId-1, 0, updateSortStr, source)
+				//该分类下的所有表格也需要+1
+				_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, 0, 0, updateSortStr, source)
+			} else {
+				//如果该分类下存在表格,且第一个表格的排序等于0,那么需要调整排序
+				firstExcel, tErr := excel.GetFirstExcelInfoByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstExcel != nil && firstExcel.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, 0, firstExcel.ExcelInfoId-1, updateSortStr, source)
+					_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr, source)
+				}
+			}
+
+			excelClassifyInfo.Sort = 0 //那就是排在第一位
+			excelClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = excelClassifyInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应分类的root_id和层级
+			if oldParentId != parentClassifyId {
+				/*if len(classifyIds) > 0 {
+					levelStep := excelClassifyInfo.Level - oldLevel
+					err = data_manage.UpdateEdbClassifyChildByParentClassifyId(classifyIds, excelClassifyInfo.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子分类失败,Err:" + err.Error())
+						return
+					}
+				}*/
+			}
+		}
+	} else {
+		if excelInfo == nil {
+			errMsg = "当前表格不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了分类,那么移动该表格数据
+		if excelInfo.ExcelClassifyId != parentClassifyId {
+			excelInfo.ExcelClassifyId = parentClassifyId
+			excelInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ExcelClassifyId", "ModifyTime")
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == excelInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, prevClassify.ExcelClassifyId, prevClassify.Sort, updateSortStr, source)
+					} else {
+						_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr, source)
+					}
+
+					//变更表格
+					if prevExcelInfo != nil {
+						//变更兄弟节点的排序
+						_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, prevExcelInfo.ExcelInfoId, updateSortStr, source)
+					} else {
+						_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr, source)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更分类
+						if prevClassify != nil {
+							_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, prevClassify.ExcelClassifyId, prevSort, updateSortStr, source)
+						} else {
+							_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr, source)
+						}
+
+						//变更表格
+						if prevExcelInfo != nil {
+							//变更兄弟节点的排序
+							_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, prevExcelInfo.ExcelInfoId, updateSortStr, source)
+						} else {
+							_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr, source)
+						}
+					}
+				}
+			}
+
+			excelInfo.Sort = prevSort + 1
+			excelInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevExcelInfo == nil && nextExcelInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetExcelClassifyMaxSort(parentClassifyId, source)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			excelInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			excelInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := excel.GetFirstExcelClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, firstClassify.ExcelClassifyId-1, 0, updateSortStr, source)
+				//该分类下的所有表格也需要+1
+				_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, 0, 0, updateSortStr, source)
+			} else {
+				//如果该分类下存在表格,且第一个表格的排序等于0,那么需要调整排序
+				firstExcel, tErr := excel.GetFirstExcelInfoByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstExcel != nil && firstExcel.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = excel.UpdateExcelInfoSortByClassifyId(parentClassifyId, 0, firstExcel.ExcelInfoId-1, updateSortStr, source)
+					_ = excel.UpdateExcelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr, source)
+				}
+			}
+
+			excelInfo.Sort = 0 //那就是排在第一位
+			excelInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = excelInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func GetChildClassifyByClassifyId(targetClassifyId int, source int) (targetList []*excel.ExcelClassifyItems, err error, errMsg string) {
+	//判断是否是挂在顶级目录下
+	targetClassify, err := excel.GetExcelClassifyById(targetClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前分类不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	tmpList, err := excel.GetExcelClassifyBySourceOrderByLevel(source)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		err = errors.New("获取数据失败,Err:" + err.Error())
+		return
+	}
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.ExcelClassifyId == targetClassify.ExcelClassifyId {
+				idMap[v.ExcelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.ExcelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ExcelClassifyId]; ok {
+				targetItem := new(excel.ExcelClassifyItems)
+				targetItem.ExcelClassifyId = v.ExcelClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+
+	return
+}

+ 1354 - 0
services/data/excel/excel_info.go

@@ -0,0 +1,1354 @@
+package excel
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/models/data_manage/excel/response"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// GetExcelDetailInfoByExcelInfoId 根据表格id获取表格详情
+func GetExcelDetailInfoByExcelInfoId(excelInfoId int) (excelDetail response.ExcelInfoDetail, errMsg string, err error) {
+	errMsg = `获取失败`
+	//获取eta表格信息
+	excelInfo, err := excel.GetExcelInfoById(excelInfoId)
+	if err != nil {
+		err = errors.New("获取ETA表格信息失败,Err:" + err.Error())
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "ETA表格被删除,请刷新页面"
+			err = errors.New("ETA表格被删除,请刷新页面,Err:" + err.Error())
+		}
+		return
+	}
+
+	return formatExcelInfo2Detail(excelInfo)
+}
+
+// GetExcelDetailInfoByUnicode 根据表格编码获取表格详情
+func GetExcelDetailInfoByUnicode(unicode string) (excelDetail response.ExcelInfoDetail, errMsg string, err error) {
+	errMsg = `获取失败`
+	// 获取eta表格信息
+	excelInfo, err := excel.GetExcelInfoByUnicode(unicode)
+	if err != nil {
+		err = errors.New("获取ETA表格信息失败,Err:" + err.Error())
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "ETA表格被删除,请刷新页面"
+			err = errors.New("ETA表格被删除,请刷新页面,Err:" + err.Error())
+		}
+		return
+	}
+
+	return formatExcelInfo2Detail(excelInfo)
+}
+
+func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo) (excelDetail response.ExcelInfoDetail, errMsg string, err error) {
+	excelDetail = response.ExcelInfoDetail{
+		ExcelInfoId:     excelInfo.ExcelInfoId,
+		Source:          excelInfo.Source,
+		ExcelType:       excelInfo.ExcelType,
+		ExcelName:       excelInfo.ExcelName,
+		UniqueCode:      excelInfo.UniqueCode,
+		ExcelClassifyId: excelInfo.ExcelClassifyId,
+		SysUserId:       excelInfo.SysUserId,
+		SysUserRealName: excelInfo.SysUserRealName,
+		Content:         excelInfo.Content,
+		ExcelImage:      excelInfo.ExcelImage,
+		FileUrl:         excelInfo.FileUrl,
+		Sort:            excelInfo.Sort,
+		IsDelete:        excelInfo.IsDelete,
+		ModifyTime:      excelInfo.ModifyTime,
+		CreateTime:      excelInfo.CreateTime,
+		TableData:       nil,
+	}
+
+	switch excelInfo.Source {
+	case utils.TIME_TABLE: // 自定义表格
+		var tableDataConfig TableDataConfig
+		err = json.Unmarshal([]byte(excelDetail.Content), &tableDataConfig)
+		if err != nil {
+			err = errors.New("表格json转结构体失败,Err:" + err.Error())
+			return
+		}
+		result, tmpErr := GetDataByTableDataConfig(tableDataConfig)
+		if tmpErr != nil {
+			err = errors.New("获取最新的表格数据失败,Err:" + tmpErr.Error())
+			return
+		}
+		excelDetail.TableData = result
+	case utils.MIXED_TABLE: // 混合表格
+		var result request.MixedTableReq
+		err = json.Unmarshal([]byte(excelDetail.Content), &result)
+		if err != nil {
+			err = errors.New("表格json转结构体失败,Err:" + err.Error())
+			return
+		}
+		newData, tmpErr, tmpErrMsg := GetMixedTableCellData(result)
+		if tmpErr != nil {
+			errMsg = "获取失败"
+			if tmpErrMsg != `` {
+				errMsg = tmpErrMsg
+			}
+			err = errors.New("获取最新的数据失败,Err:" + tmpErr.Error())
+			return
+		}
+		result.Data = newData
+		excelDetail.TableData = result
+	}
+	return
+}
+
+// GetExcelInfoOpButton 获取ETA表格的操作权限
+func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int) (button response.ExcelInfoDetailButton) {
+	//非管理员角色查看其他用户创建的表格,可刷新、另存为、下载表格;
+	button.RefreshButton = true
+	button.CopyButton = true
+	button.DownloadButton = true
+
+	// 1、本用户创建的表格,可编辑、刷新、另存为、下载、删除,删除需二次确认;
+	// 2、管理员角色对所有表格有如上权限;
+	// 3、在线excel所有人都能编辑
+	if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN || sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_FICC_ADMIN || sysUser.AdminId == belongUserId || source == utils.EXCEL_DEFAULT {
+		button.OpButton = true
+		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
+}
+
+// GetFirstEdbDataList 获取第一列的数据
+func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList []string) (resultDataList []request.ManualDataReq, err error) {
+	var dataList []*data_manage.EdbDataList
+	switch edbInfo.EdbInfoType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+	default:
+		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+	}
+	if err != nil {
+		return
+	}
+
+	// 获取需要的期数
+	lenData := len(dataList)
+	if lenData <= 0 {
+		return
+	}
+
+	tmpManualDateNum := 0 // 手工数据的期数
+	lenManualDate := len(manualDateList)
+	if lenManualDate > 0 {
+		sortDateList := manualDateList
+		baseDateList := utils.StrArr{}
+		baseDateList = append(baseDateList, sortDateList...)
+		sort.Sort(baseDateList)
+		sortDateList = append([]string{}, baseDateList...)
+
+		lastData := dataList[lenData-1]
+		lastDataDate, tmpErr := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 遍历倒序后的日期,然后匹配在实际数据之后日期的个数
+		for _, tmpDateStr := range sortDateList {
+			tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			if tmpDate.After(lastDataDate) {
+				tmpManualDateNum++
+				continue
+			}
+
+			break
+		}
+
+	}
+
+	// 需要的期数减去手工数据的期数,这才是A列指标需要的数据
+	num = num - tmpManualDateNum
+	if num > lenData {
+		num = lenData
+	}
+
+	latestDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfo.LatestDate, time.Local)
+	for i := 1; i <= num; i++ {
+		dataTime, _ := time.ParseInLocation(utils.FormatDate, dataList[lenData-i].DataTime, time.Local)
+		dataType := 1
+		// 如果是预测指标,且当前值的日期,晚于实际日期,那么是预测值
+		if edbInfo.EdbInfoType == 1 && dataTime.After(latestDateTime) {
+			dataType = 5
+		}
+
+		resultDataList = append(resultDataList, request.ManualDataReq{
+			DataType:     dataType,
+			DataTime:     dataList[lenData-i].DataTime,
+			ShowValue:    fmt.Sprint(dataList[lenData-i].Value),
+			Value:        fmt.Sprint(dataList[lenData-i].Value),
+			DataTimeType: 1,
+		})
+	}
+
+	return
+}
+
+// GetOtherEdbDataList 获取其他列的数据
+func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resultDataList []request.ManualDataReq, err error) {
+	lenDate := len(dateList)
+	if lenDate <= 0 {
+		return
+	}
+	sortDateList := dateList
+	baseDateList := utils.StrArr{}
+	baseDateList = append(baseDateList, sortDateList...)
+	sort.Sort(baseDateList)
+	sortDateList = append([]string{}, baseDateList...)
+
+	endDateTime, err := time.ParseInLocation(utils.FormatDate, sortDateList[0], time.Local)
+	if err != nil {
+		return
+	}
+
+	firstDateTime, err := time.ParseInLocation(utils.FormatDate, sortDateList[lenDate-1], time.Local)
+	if err != nil {
+		return
+	}
+
+	var dataList []*data_manage.EdbDataList
+	switch edbInfo.EdbInfoType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+	default:
+		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+	}
+	if err != nil {
+		return
+	}
+
+	// 获取日期内的数据(包含开始日期前一个日期,以及 结束日期后一个日期,目的为了做空日期时的 插值法兼容)
+	baseDataList := make([]*data_manage.EdbDataList, 0)
+	var lastData *data_manage.EdbDataList
+	var isInsert bool
+	for _, data := range dataList {
+		tmpDate := data.DataTime
+		tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDate, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		if tmpDateTime.Before(firstDateTime) {
+			lastData = data
+			continue
+		}
+
+		// 如果是第一次写入数据
+		if !isInsert && lastData != nil {
+			baseDataList = append(baseDataList, lastData)
+		}
+
+		if tmpDateTime.After(endDateTime) {
+			baseDataList = append(baseDataList, data)
+			break
+		}
+		baseDataList = append(baseDataList, data)
+		isInsert = true
+	}
+
+	// 实际数据的日期map
+	realValMap := make(map[string]string)
+	for _, v := range baseDataList {
+		realValMap[v.DataTime] = v.DataTime
+	}
+
+	// 插值法处理
+	handleDataMap := make(map[string]float64)
+	err = data.HandleDataByLinearRegression(baseDataList, handleDataMap)
+	if err != nil {
+		return
+	}
+
+	latestDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfo.LatestDate, time.Local)
+
+	// 对于不存在的数据做补充
+	for _, date := range sortDateList {
+		dataType := 1
+		if _, ok := realValMap[date]; !ok {
+			dataType = 2
+		} else {
+			dataTime, _ := time.ParseInLocation(utils.FormatDate, date, time.Local)
+			// 如果是预测指标,且当前值的日期,晚于实际日期,那么是预测值
+			if edbInfo.EdbInfoType == 1 && dataTime.After(latestDateTime) {
+				dataType = 5
+			}
+		}
+		var value, showValue string
+		if tmpVal, ok := handleDataMap[date]; ok {
+			value = fmt.Sprint(tmpVal)
+			showValue = value
+		} else {
+			dataType = 3
+		}
+		resultDataList = append(resultDataList, request.ManualDataReq{
+			DataType:  dataType,
+			DataTime:  date,
+			ShowValue: showValue,
+			Value:     value,
+		})
+	}
+
+	return
+}
+
+// GetFirstHistoryEdbDataList 获取指标的历史的数据
+func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate string) (resultDataList []request.ManualDataReq, err error) {
+	endDateTime, err := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+	if err != nil {
+		return
+	}
+
+	var dataList []*data_manage.EdbDataList
+	switch edbInfo.EdbInfoType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, endDate)
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, endDate, true)
+	default:
+		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+	}
+	if err != nil {
+		return
+	}
+
+	// 获取需要的期数
+	lenData := len(dataList)
+	if lenData <= 0 {
+		return
+	}
+	lastData := dataList[lenData-1]
+	lastDataDateTime, err := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+	if err != nil {
+		return
+	}
+	if endDateTime.Equal(lastDataDateTime) || lastDataDateTime.After(endDateTime) {
+		dataList = dataList[:lenData-1]
+		lenData = len(dataList)
+	}
+	if num > lenData {
+		num = lenData
+	}
+
+	latestDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfo.LatestDate, time.Local)
+	for i := 1; i <= num; i++ {
+		dataTime, _ := time.ParseInLocation(utils.FormatDate, dataList[lenData-i].DataTime, time.Local)
+		dataType := 1
+		// 如果是预测指标,且当前值的日期,晚于实际日期,那么是预测值
+		if edbInfo.EdbInfoType == 1 && dataTime.After(latestDateTime) {
+			dataType = 5
+		}
+
+		resultDataList = append(resultDataList, request.ManualDataReq{
+			DataType:  dataType,
+			DataTime:  dataList[lenData-i].DataTime,
+			ShowValue: fmt.Sprint(dataList[lenData-i].Value),
+			Value:     fmt.Sprint(dataList[lenData-i].Value),
+		})
+	}
+
+	return
+}
+
+type TableDataConfig struct {
+	EdbInfoIdList    []int                     `description:"指标id列表,从左至右,从上到下的顺序"`
+	Sort             int                       `description:"日期排序,0:倒序,1:正序"`
+	Data             []ManualData              `description:"数据列表"`
+	Num              int                       `description:"实际数据需要列出来的期数"`
+	RemoveDate       []string                  `description:"不展示的日期"`
+	ManualDate       []string                  `description:"手动配置的日期(未来的日期)"`
+	TableEdbInfoList []TableEdbInfo            `description:"表格内指标信息"`
+	TextRowData      [][]request.ManualDataReq `description:"文本列表"`
+}
+
+type TableEdbInfo struct {
+	EdbInfoId    int    `description:"指标ID"`
+	Tag          string `description:"标签"`
+	EdbName      string `description:"指标名称"`
+	EdbAliasName string `description:"指标别名"`
+	Frequency    string `description:"频度"`
+	Unit         string `description:"单位"`
+}
+
+type ManualData struct {
+	DataType            int                       `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算"`
+	DataTime            string                    `description:"所属日期"`
+	DataTimeType        int                       `description:"日期类型,1:实际日期;2:未来日期"`
+	ShowValue           string                    `description:"展示值"`
+	Value               string                    `description:"实际值(计算公式)"`
+	EdbInfoId           int                       `description:"指标id"`
+	Tag                 string                    `description:"下标"`
+	RelationEdbInfoList []request.RelationEdbInfo `description:"关联指标(计算公式中关联的指标,用于计算的时候去匹配)"`
+}
+
+// GetTableDataConfig 根据TableDataReq配置获取相关数据配置
+func GetTableDataConfig(reqData request.TableDataReq) (tableDataConfig TableDataConfig, err error) {
+	// 指标数据
+	tableDataConfig.EdbInfoIdList = reqData.EdbInfoIdList
+	tableDataConfig.Sort = reqData.Sort
+
+	if len(reqData.Data) <= 0 {
+		err = errors.New("数据不能为空")
+		return
+	}
+
+	// 开始日期
+	var startDate string
+	// A列的指标id
+	var firstEdbInfoId int
+	// 手工操作的数据列
+	manualDataList := make([]ManualData, 0)
+	// 指标配置列表
+	tableEdbInfoList := make([]TableEdbInfo, 0)
+
+	// 第一列的日期map
+	firstDateMap := make(map[string]string)
+	manualDateMap := make(map[string]string)
+
+	for _, v := range reqData.Data {
+		// 指标信息
+		tmpTableEdbInfo := TableEdbInfo{
+			EdbInfoId:    v.EdbInfoId,
+			Tag:          v.Tag,
+			EdbName:      v.EdbName,
+			EdbAliasName: v.EdbAliasName,
+			Frequency:    v.Frequency,
+			Unit:         v.Unit,
+		}
+		tableEdbInfoList = append(tableEdbInfoList, tmpTableEdbInfo)
+
+		// 确定数据A列
+		if v.Tag == "A" {
+			firstEdbInfoId = v.EdbInfoId
+			lenData := len(v.Data)
+			if lenData <= 0 {
+				err = errors.New("A列不能为空")
+				return
+			}
+			index := 0
+			if reqData.Sort == 1 {
+				// 倒序
+				index = lenData - 1
+			}
+
+			startDate = v.Data[index].DataTime
+
+			// 存在的日期列表
+			for _, data := range v.Data {
+				firstDateMap[data.DataTime] = data.DataTime
+
+				if data.DataTimeType == 2 {
+					manualDateMap[data.DataTime] = data.DataTime
+				}
+			}
+		}
+
+		for _, data := range v.Data {
+			if data.DataType == 3 || data.DataType == 4 {
+				tmpManualData := ManualData{
+					DataType:            data.DataType,
+					DataTime:            data.DataTime,
+					DataTimeType:        data.DataTimeType,
+					ShowValue:           data.ShowValue,
+					Value:               data.Value,
+					EdbInfoId:           v.EdbInfoId,
+					Tag:                 v.Tag,
+					RelationEdbInfoList: data.RelationEdbInfoList,
+				}
+				if data.DataType == 4 {
+					tmpManualData.ShowValue = ``
+				}
+				manualDataList = append(manualDataList, tmpManualData)
+			}
+		}
+	}
+
+	// 总共需要的期数
+	num := len(reqData.Data[0].Data)
+
+	removeDate := make([]string, 0)
+
+	// 获取期数
+	{
+		firstDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(firstEdbInfoId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		var firstDataList []*data_manage.EdbDataList
+		switch edbInfo.EdbInfoType {
+		case 0:
+			firstDataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+		case 1:
+			_, firstDataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+		default:
+			err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+		}
+		if err != nil {
+			return
+		}
+
+		// 获取日期内的数据(包含开始日期前一个日期,以及 结束日期后一个日期,目的为了做空日期时的 插值法兼容)
+		baseDataList := make([]*data_manage.EdbDataList, 0)
+		for _, data := range firstDataList {
+			tmpDate := data.DataTime
+			tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDate, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			if tmpDateTime.Before(firstDateTime) {
+				continue
+			}
+			baseDataList = append(baseDataList, data)
+		}
+
+		// 筛选出需要删除的日期
+		for _, tmpData := range baseDataList {
+			//firstDateMap{}
+			if _, ok := firstDateMap[tmpData.DataTime]; !ok {
+				removeDate = append(removeDate, tmpData.DataTime)
+			}
+		}
+	}
+
+	tableDataConfig.Num = num
+	tableDataConfig.RemoveDate = removeDate
+	tableDataConfig.Data = manualDataList
+	tableDataConfig.TableEdbInfoList = tableEdbInfoList
+	tableDataConfig.TextRowData = reqData.TextRowData
+
+	return
+}
+
+// GetDataByTableDataConfig 根据数据配置获取表格数据
+func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp request.TableDataReq, err error) {
+	// 没有选择指标的情况下,直接返回吧
+	if len(tableDataConfig.EdbInfoIdList) <= 0 {
+		return
+	}
+
+	// 实际期数没有的情况下,直接返回吧
+	if tableDataConfig.Num <= 0 {
+		return
+	}
+
+	// 获取所有的指标信息
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	edbInfoIdList := make([]int, 0)
+	// 标签与指标id的map
+	tagEdbInfoIdMap := make(map[string]int)
+	{
+		for _, tableEdbInfo := range tableDataConfig.TableEdbInfoList {
+			edbInfoIdList = append(edbInfoIdList, tableEdbInfo.EdbInfoId)
+			tagEdbInfoIdMap[tableEdbInfo.Tag] = tableEdbInfo.EdbInfoId
+		}
+		edbInfoList, tmpErr := data_manage.GetEdbInfoByIdList(edbInfoIdList)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		for _, v := range edbInfoList {
+			edbInfoMap[v.EdbInfoId] = v
+		}
+	}
+
+	manualDateMap := make(map[string]string, 0)
+	manualDateList := make([]string, 0)
+	for _, v := range tableDataConfig.Data {
+		if _, ok := manualDateMap[v.DataTime]; !ok {
+			manualDateMap[v.DataTime] = v.DataTime
+			manualDateList = append(manualDateList, v.DataTime)
+		}
+	}
+
+	// 寻找A列的数据列表
+	firstEdbInfo, ok := edbInfoMap[tableDataConfig.TableEdbInfoList[0].EdbInfoId]
+	if !ok {
+		err = errors.New("找不到A列指标")
+		return
+	}
+	baseFirstEdbInfoDataList, err := GetFirstEdbDataList(firstEdbInfo, tableDataConfig.Num, manualDateList)
+	if err != nil {
+		return
+	}
+	// A列找不到数据,那么就直接返回吧
+	if len(baseFirstEdbInfoDataList) <= 0 {
+		return
+	}
+
+	firstEdbInfoDataList := make([]request.ManualDataReq, 0)
+	if tableDataConfig.RemoveDate != nil && len(tableDataConfig.RemoveDate) > 0 {
+		for _, v := range baseFirstEdbInfoDataList {
+			if utils.InArrayByStr(tableDataConfig.RemoveDate, v.DataTime) {
+				continue
+			}
+			firstEdbInfoDataList = append(firstEdbInfoDataList, v)
+		}
+	} else {
+		firstEdbInfoDataList = baseFirstEdbInfoDataList
+	}
+	if len(firstEdbInfoDataList) <= 0 {
+		return
+	}
+	// 实际数据的最后一天
+	lastRealDateTime, err := time.ParseInLocation(utils.FormatDate, firstEdbInfoDataList[0].DataTime, time.Local)
+	if err != nil {
+		return
+	}
+
+	dateMap := make(map[string]string)
+	dateList := make([]string, 0)
+	edbInfoIdDateDataMap := make(map[int]map[string]request.ManualDataReq)
+	firstDateDataMap := make(map[string]request.ManualDataReq)
+	for _, v := range firstEdbInfoDataList {
+		dateList = append(dateList, v.DataTime)
+		dateMap[v.DataTime] = v.DataTime
+		firstDateDataMap[v.DataTime] = v
+	}
+
+	// 将手工数据的日期填补进去(未来的日期,过去的就不管了)
+	for _, manualData := range tableDataConfig.Data {
+		if !utils.InArrayByStr(dateList, manualData.DataTime) {
+			tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, manualData.DataTime, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			if tmpDateTime.After(lastRealDateTime) {
+				dateList = append(dateList, manualData.DataTime)
+			}
+		}
+	}
+
+	edbInfoIdDateDataMap[firstEdbInfo.EdbInfoId] = firstDateDataMap
+
+	for k, edbInfoId := range tableDataConfig.EdbInfoIdList {
+		if k == 0 {
+			continue
+		}
+
+		tmpEdbInfo, ok := edbInfoMap[edbInfoId]
+		if !ok {
+			err = errors.New("找不到A列指标")
+			return
+		}
+		otherDataList, tmpErr := GetOtherEdbDataList(tmpEdbInfo, dateList)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		tmpDateDataMap := make(map[string]request.ManualDataReq)
+		for _, v := range otherDataList {
+			tmpDateDataMap[v.DataTime] = v
+		}
+		edbInfoIdDateDataMap[tmpEdbInfo.EdbInfoId] = tmpDateDataMap
+	}
+
+	for _, v := range tableDataConfig.Data {
+		tmpDate := v.DataTime
+		if _, ok := dateMap[tmpDate]; !ok {
+			dateMap[v.DataTime] = tmpDate
+		}
+
+		edbInfoIdDateData, ok := edbInfoIdDateDataMap[v.EdbInfoId]
+		if !ok {
+			edbInfoIdDateData = make(map[string]request.ManualDataReq)
+		}
+
+		// 判断是否存在该日期的数据(不存在,那么插入数据吧,存在就不管了)
+		tmpManualData, ok := edbInfoIdDateData[tmpDate]
+		if !ok {
+			edbInfoIdDateData[tmpDate] = request.ManualDataReq{
+				DataType:  v.DataType,
+				DataTime:  v.DataTime,
+				ShowValue: v.ShowValue,
+				Value:     v.Value,
+			}
+		} else {
+			if (tmpManualData.DataType == 3 || tmpManualData.DataType == 4) && tmpManualData.ShowValue == `` {
+				tmpManualData.DataType = v.DataType
+				tmpManualData.ShowValue = v.ShowValue
+				tmpManualData.Value = v.Value
+				tmpManualData.RelationEdbInfoList = v.RelationEdbInfoList
+
+				edbInfoIdDateData[tmpDate] = tmpManualData
+			}
+		}
+		edbInfoIdDateDataMap[v.EdbInfoId] = edbInfoIdDateData
+	}
+
+	// 获取数据的日期排序
+	sortDateTimeList := make([]time.Time, 0)
+	{
+		sortDateList := dateList
+		if tableDataConfig.Sort == 1 {
+			baseDateList := utils.StrArr{}
+			baseDateList = append(baseDateList, sortDateList...)
+			sort.Sort(baseDateList)
+			sortDateList = append([]string{}, baseDateList...)
+		} else {
+			sort.Strings(sortDateList)
+		}
+		for _, v := range sortDateList {
+			tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, v, time.Local)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			sortDateTimeList = append(sortDateTimeList, tmpDateTime)
+		}
+	}
+
+	// 数据处理,处理成表格的数据格式
+	tableDataMap, textRowListDataResp := handleTable(tagEdbInfoIdMap, lastRealDateTime, sortDateTimeList, edbInfoIdDateDataMap, tableDataConfig.Data, tableDataConfig.TextRowData)
+
+	data := make([]request.EdbInfoData, 0)
+	for _, tableEdbInfo := range tableDataConfig.TableEdbInfoList {
+		tagEdbInfoIdMap[tableEdbInfo.Tag] = tableEdbInfo.EdbInfoId
+
+		manualDataReqList := make([]request.ManualDataReq, 0)
+		tmpEdbInfoData := request.EdbInfoData{
+			EdbInfoId:    tableEdbInfo.EdbInfoId,
+			Tag:          tableEdbInfo.Tag,
+			EdbName:      tableEdbInfo.EdbName,
+			EdbAliasName: tableEdbInfo.EdbAliasName,
+			Frequency:    tableEdbInfo.Frequency,
+			Unit:         tableEdbInfo.Unit,
+			Data:         manualDataReqList,
+		}
+
+		edbInfo, ok := edbInfoMap[tableEdbInfo.EdbInfoId]
+		if ok {
+			tmpEdbInfoData.EdbName = edbInfo.EdbName
+			tmpEdbInfoData.Frequency = edbInfo.Frequency
+			tmpEdbInfoData.Unit = edbInfo.Unit
+		}
+
+		for index, dateTime := range sortDateTimeList {
+			dataTimeType := 1
+			if dateTime.After(lastRealDateTime) {
+				dataTimeType = 2
+			}
+			tmpDateTimeStr := dateTime.Format(utils.FormatDate)
+
+			rowData, ok := tableDataMap[index+1]
+			if !ok {
+				manualDataReqList = append(manualDataReqList, request.ManualDataReq{
+					DataType:            3,
+					DataTime:            tmpDateTimeStr,
+					DataTimeType:        dataTimeType,
+					ShowValue:           "",
+					Value:               "",
+					RelationEdbInfoList: nil,
+				})
+				continue
+			}
+
+			tmpData, ok := rowData[tableEdbInfo.Tag]
+			if !ok {
+				manualDataReqList = append(manualDataReqList, request.ManualDataReq{
+					DataType:            3,
+					DataTime:            tmpDateTimeStr,
+					DataTimeType:        dataTimeType,
+					ShowValue:           "",
+					Value:               "",
+					RelationEdbInfoList: nil,
+				})
+				continue
+			}
+
+			tmpData.DataTimeType = dataTimeType
+			manualDataReqList = append(manualDataReqList, tmpData)
+		}
+
+		tmpEdbInfoData.Data = manualDataReqList
+
+		data = append(data, tmpEdbInfoData)
+	}
+
+	// 处理一下数据格式
+	for _, d := range data {
+		for k2, d2 := range d.Data {
+			// 可能有ShowValue非数值, 转换一下报错则continue
+			vf, e := strconv.ParseFloat(d2.ShowValue, 64)
+			if e != nil {
+				continue
+			}
+			d.Data[k2].ShowValue = utils.FormatTableDataShowValue(vf)
+		}
+	}
+	for _, d := range textRowListDataResp {
+		for k2, d2 := range d {
+			// 可能有ShowValue非数值, 转换一下报错则continue
+			vf, e := strconv.ParseFloat(d2.ShowValue, 64)
+			if e != nil {
+				continue
+			}
+			d[k2].ShowValue = utils.FormatTableDataShowValue(vf)
+		}
+	}
+
+	resultResp = request.TableDataReq{
+		EdbInfoIdList: edbInfoIdList,
+		Sort:          tableDataConfig.Sort,
+		TextRowData:   textRowListDataResp,
+		Data:          data,
+	}
+
+	return
+}
+
+// handleTable 表格数据处理
+func handleTable(tagEdbInfoIdMap map[string]int, lastRealDateTime time.Time, sortDateTimeList []time.Time, edbInfoIdDateDataMap map[int]map[string]request.ManualDataReq, manualDataList []ManualData, textRowData [][]request.ManualDataReq) (tableDataMap map[int]map[string]request.ManualDataReq, textRowListDataResp [][]request.ManualDataReq) {
+	tagList := make([]string, 0)
+	for tag, _ := range tagEdbInfoIdMap {
+		tagList = append(tagList, tag)
+	}
+	sort.Strings(tagList)
+
+	tableDataMap = make(map[int]map[string]request.ManualDataReq) //行、列数据
+
+	// 日期与行的关系
+	dateIndexMap := make(map[string]int)
+
+	for k, dateTime := range sortDateTimeList {
+		rowDataMap := make(map[string]request.ManualDataReq)
+
+		dataTimeType := 1
+		if dateTime.After(lastRealDateTime) {
+			dataTimeType = 2
+		}
+
+		tmpDateTimeStr := dateTime.Format(utils.FormatDate)
+		dateIndexMap[tmpDateTimeStr] = k + 1
+
+		for _, tag := range tagList {
+			edbInfoId, ok := tagEdbInfoIdMap[tag]
+			if !ok { // 没有找到该指标的映射关系,那么就用空串填补
+				rowDataMap[tag] = request.ManualDataReq{
+					DataType:            3,
+					DataTime:            tmpDateTimeStr,
+					DataTimeType:        dataTimeType,
+					ShowValue:           "",
+					Value:               "",
+					RelationEdbInfoList: nil,
+				}
+				continue
+			}
+
+			// 获取指标的数据map
+			dateDataMap, ok := edbInfoIdDateDataMap[edbInfoId]
+			if !ok { // 没有找到该指标的数据,那么就用空串填补
+				rowDataMap[tag] = request.ManualDataReq{
+					DataType:            3,
+					DataTime:            tmpDateTimeStr,
+					DataTimeType:        dataTimeType,
+					ShowValue:           "",
+					Value:               "",
+					RelationEdbInfoList: nil,
+				}
+				continue
+			}
+
+			// 获取指标该日期的数据
+			tmpData, ok := dateDataMap[tmpDateTimeStr]
+			if !ok { // 该指标没有找到对应日期的数据,那么就用空串填补
+				rowDataMap[tag] = request.ManualDataReq{
+					DataType:            3,
+					DataTime:            tmpDateTimeStr,
+					DataTimeType:        dataTimeType,
+					ShowValue:           "",
+					Value:               "",
+					RelationEdbInfoList: nil,
+				}
+				continue
+			}
+			tmpData.DataTimeType = dataTimeType
+			rowDataMap[tag] = tmpData
+		}
+		tableDataMap[k+1] = rowDataMap
+	}
+
+	// 替换手工设置的数据
+	for _, manualData := range manualDataList {
+		// 找不到该日期,说明这日期过期了,不处理
+		index, ok := dateIndexMap[manualData.DataTime]
+		if !ok {
+			continue
+		}
+
+		// 获取对应行的数据
+		rowDataMap, ok := tableDataMap[index]
+		if !ok {
+			continue
+		}
+
+		// 找到对应的单元格
+		tmpData, ok := rowDataMap[manualData.Tag]
+		if !ok {
+			continue
+		}
+
+		// 如果该单元格实际有数据(包含预测值),或者插值法补充了数据的话,那么就不用手动填入的数据
+		if utils.InArrayByInt([]int{1, 2, 5}, tmpData.DataType) {
+			continue
+		}
+
+		// 手工填写的数字
+		if tmpData.DataType == 3 {
+			tmpData.ShowValue = manualData.ShowValue
+			tmpData.Value = manualData.Value
+			tableDataMap[index][manualData.Tag] = tmpData
+
+			//edbInfoIdDateDataMap[manualData.EdbInfoId][manualData.DataTime] = tmpData
+			continue
+		}
+
+		// 公式
+		tmpData.DataType = manualData.DataType
+		tmpData.ShowValue = ``
+		tmpData.Value = manualData.Value
+		tmpData.RelationEdbInfoList = manualData.RelationEdbInfoList
+		tableDataMap[index][manualData.Tag] = tmpData
+
+	}
+
+	// 文本行的列表插入
+	lenTableData := len(tableDataMap)
+
+	// 文本行第一列的数据列表(可能多行)
+	firstColTextRowList := make([]request.ManualDataReq, 0)
+	// 参与计算的文本行列表数据
+	tmpTextRowList := make([][]request.ManualDataReq, 0)
+	for k, textRowList := range textRowData {
+		// 判断列数是否匹配,不匹配的话那么过滤
+		if len(tagList)+1 != len(textRowList) {
+			continue
+		}
+		rowDataMap := make(map[string]request.ManualDataReq)
+		tmpTextRow := make([]request.ManualDataReq, 0)
+		for index, textRow := range textRowList {
+			// 移除第一列,因为第一列是日期列
+			if index == 0 {
+				firstColTextRowList = append(firstColTextRowList, textRow)
+				continue
+			}
+			rowDataMap[tagList[index-1]] = textRow
+			tmpTextRow = append(tmpTextRow, textRow)
+		}
+
+		tableDataMap[lenTableData+k+1] = rowDataMap
+		tmpTextRowList = append(tmpTextRowList, tmpTextRow)
+	}
+
+	// 参与计算的单元格
+	calculateCellMap := make(map[string]string)
+
+	// 计算手工填写的单元格
+	for _, manualData := range manualDataList {
+		// 找不到该日期,说明这日期过期了,不处理
+		index, ok := dateIndexMap[manualData.DataTime]
+		if !ok {
+			continue
+		}
+
+		// 获取对应行的数据
+		rowDataMap, ok := tableDataMap[index]
+		if !ok {
+			continue
+		}
+
+		// 找到对应的单元格
+		colData, ok := rowDataMap[manualData.Tag]
+		if !ok {
+			continue
+		}
+
+		// 如果该单元格不是计算公式的单元格,那么直接退出当前循环即可
+		if colData.DataType != 4 {
+			continue
+		}
+
+		tagMap := make(map[string]float64)
+		lenRelation := len(colData.RelationEdbInfoList)
+		replaceNum := 0
+		for _, relation := range colData.RelationEdbInfoList {
+			relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row
+			valStr, tmpErr := getCalculateValue(tableDataMap, relation.Tag, relation.Row, calculateCellMap)
+			if tmpErr != nil {
+				continue
+			}
+			tmpValDecimal, tmpErr := decimal.NewFromString(valStr)
+			if tmpErr != nil {
+				continue
+			}
+			tagMap[relationCellTagName], _ = tmpValDecimal.Float64()
+			replaceNum++
+		}
+
+		// 如果替换的数据与关联的不一致,那么就退出当前循环
+		if lenRelation != replaceNum {
+			continue
+		}
+
+		// 计算
+		val, _, err := calculate(strings.ToUpper(colData.Value), tagMap)
+		// 计算失败,退出循环
+		if err != nil {
+			continue
+		}
+		// 重新赋值
+		colData.ShowValue = val
+		tableDataMap[index][manualData.Tag] = colData
+
+	}
+
+	// 计算文本行的单元格
+	for k, textRow := range tmpTextRowList {
+		// 获取对应行的数据
+		index := lenTableData + k + 1
+		rowDataMap, ok := tableDataMap[index]
+		if !ok {
+			continue
+		}
+
+		for colIndex, _ := range textRow {
+			currTag := tagList[colIndex]
+			// 找到对应的单元格
+			colData, ok := rowDataMap[currTag]
+			if !ok {
+				continue
+			}
+
+			// 如果该单元格不是计算公式的单元格,那么直接退出当前循环即可
+			if colData.DataType != 4 {
+				continue
+			}
+
+			tagMap := make(map[string]float64)
+			lenRelation := len(colData.RelationEdbInfoList)
+			replaceNum := 0
+			for _, relation := range colData.RelationEdbInfoList {
+				relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row
+				valStr, tmpErr := getCalculateValue(tableDataMap, relation.Tag, relation.Row, calculateCellMap)
+				if tmpErr != nil {
+					continue
+				}
+				tmpValDecimal, tmpErr := decimal.NewFromString(valStr)
+				if tmpErr != nil {
+					continue
+				}
+				tagMap[relationCellTagName], _ = tmpValDecimal.Float64()
+				replaceNum++
+			}
+
+			// 如果替换的数据与关联的不一致,那么就退出当前循环
+			if lenRelation != replaceNum {
+				continue
+			}
+
+			// 计算
+			val, _, err := calculate(strings.ToUpper(colData.Value), tagMap)
+			// 计算失败,退出循环
+			if err != nil {
+				continue
+			}
+			// 重新赋值
+			colData.ShowValue = val
+			tableDataMap[index][currTag] = colData
+		}
+
+	}
+
+	// 计算文本行第一列的数据值(多行)
+	for k, colData := range firstColTextRowList {
+		// 如果该单元格不是计算公式的单元格,那么直接退出当前循环即可
+		if colData.DataType != 4 {
+			continue
+		}
+
+		tagMap := make(map[string]float64)
+		lenRelation := len(colData.RelationEdbInfoList)
+		replaceNum := 0
+		for _, relation := range colData.RelationEdbInfoList {
+			relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row
+			valStr, tmpErr := getCalculateValue(tableDataMap, relation.Tag, relation.Row, calculateCellMap)
+			if tmpErr != nil {
+				continue
+			}
+			tmpValDecimal, tmpErr := decimal.NewFromString(valStr)
+			if tmpErr != nil {
+				continue
+			}
+			tagMap[relationCellTagName], _ = tmpValDecimal.Float64()
+			replaceNum++
+		}
+
+		// 如果替换的数据与关联的不一致,那么就退出当前循环
+		if lenRelation != replaceNum {
+			continue
+		}
+
+		// 计算
+		val, _, err := calculate(strings.ToUpper(colData.Value), tagMap)
+		// 计算失败,退出循环
+		if err != nil {
+			continue
+		}
+		// 重新赋值
+		colData.ShowValue = val
+		firstColTextRowList[k] = colData
+	}
+
+	{
+		// 文本行的数据处理返回
+		textRowListDataResp = make([][]request.ManualDataReq, 0)
+		newLenTableDataMap := len(tableDataMap)
+		// 文本行的第一行所在的位置
+		firstTextRow := lenTableData + 1
+		for i := firstTextRow; i <= newLenTableDataMap; i++ {
+			textRowDataResp := make([]request.ManualDataReq, 0)
+
+			textRowDataResp = append(textRowDataResp, firstColTextRowList[i-firstTextRow])
+			for _, tmpTag := range tagList {
+				textRowDataResp = append(textRowDataResp, tableDataMap[i][tmpTag])
+			}
+			textRowListDataResp = append(textRowListDataResp, textRowDataResp)
+		}
+
+	}
+
+	return
+}
+
+// getCalculateValue 获取公式计算的结果
+func getCalculateValue(tableDataMap map[int]map[string]request.ManualDataReq, tag, row string, calculateCellMap map[string]string) (val string, err error) {
+	rowInt, err := strconv.Atoi(row)
+	if err != nil {
+		return
+	}
+
+	// 单元格的标签名
+	cellTagName := strings.ToUpper(tag) + row
+	val, ok := calculateCellMap[cellTagName]
+	if ok {
+		return
+	}
+
+	// 查找行数据
+	rowData, ok := tableDataMap[rowInt]
+	if !ok {
+		err = errors.New("查找" + row + "行的数据失败")
+		return
+	}
+
+	// 查找单元格数据
+	colData, ok := rowData[tag]
+	if !ok {
+		err = errors.New("查找单元格" + tag + row + "的数据失败")
+		return
+	}
+
+	// 如果不是计算单元格
+	if colData.DataType != 4 {
+		val = colData.ShowValue
+		return
+	}
+
+	// 如果是计算单元格
+	calculateCellMap[cellTagName] = ``
+
+	tagMap := make(map[string]float64)
+	for _, relation := range colData.RelationEdbInfoList {
+		relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row
+		valStr, tmpErr := getCalculateValue(tableDataMap, relation.Tag, relation.Row, calculateCellMap)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		tmpValDecimal, tmpErr := decimal.NewFromString(valStr)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		tagMap[relationCellTagName], _ = tmpValDecimal.Float64()
+	}
+
+	// 计算
+	val, _, err = calculate(strings.ToUpper(colData.Value), tagMap)
+	if err != nil {
+		return
+	}
+	// 重新赋值
+	colData.ShowValue = val
+	tableDataMap[rowInt][tag] = colData
+	calculateCellMap[cellTagName] = val
+
+	return
+}
+
+// calculate 公式计算
+func calculate(calculateFormula string, TagMap map[string]float64) (calVal, errMsg string, err error) {
+	if calculateFormula == "" {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+	calculateFormula = strings.TrimPrefix(calculateFormula, "=")
+	calculateFormula = strings.Replace(calculateFormula, "(", "(", -1)
+	calculateFormula = strings.Replace(calculateFormula, ")", ")", -1)
+	calculateFormula = strings.Replace(calculateFormula, ",", ",", -1)
+	calculateFormula = strings.Replace(calculateFormula, "。", ".", -1)
+	calculateFormula = strings.Replace(calculateFormula, "%", "*0.01", -1)
+
+	formulaFormStr := utils.ReplaceFormula(TagMap, calculateFormula)
+	//计算公式异常,那么就移除该指标
+	if formulaFormStr == `` {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	expression := formula.NewExpression(formulaFormStr)
+	calResult, err := expression.Evaluate()
+	if err != nil {
+		errMsg = "计算失败"
+		err = errors.New("计算失败:Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
+		// 分母为0的报错
+		if strings.Contains(err.Error(), "divide by zero") {
+			errMsg = "分母不能为0"
+			err = errors.New("分母不能为空,计算公式:" + formulaFormStr)
+		}
+		return
+	}
+	// 如果计算结果是NAN,那么就提示报错
+	if calResult.IsNan() {
+		errMsg = "计算失败"
+		err = errors.New("计算失败:计算结果是:NAN;formulaStr:" + formulaFormStr)
+		return
+	}
+	calVal = calResult.String()
+
+	// 转Decimal然后四舍五入
+	valDecimal, err := decimal.NewFromString(calVal)
+	if err != nil {
+		errMsg = "计算失败"
+		err = errors.New("计算失败,结果转 Decimal 失败:Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
+		return
+	}
+	calVal = valDecimal.Round(4).String()
+
+	return
+}
+
+// GetEdbIdsFromExcelCodes 获取表格中的指标IDs
+func GetEdbIdsFromExcelCodes(excelCodes []string) (edbIds []int, err error) {
+	edbIds = make([]int, 0)
+	edbIdExist := make(map[int]bool)
+	for _, v := range excelCodes {
+		// 表格详情
+		detail, msg, e := GetExcelDetailInfoByUnicode(v)
+		if e != nil {
+			err = fmt.Errorf("GetExcelDetailInfoByExcelInfoId err: %s, errMsg: %s", e.Error(), msg)
+			return
+		}
+
+		// 自定义表格
+		if detail.Source == utils.TIME_TABLE {
+			jsonByte, e := json.Marshal(detail.TableData)
+			if e != nil {
+				err = fmt.Errorf("JSON格式化自定义表格数据失败, Err: %s", e.Error())
+				return
+			}
+			var tableData request.TableDataReq
+			if e = json.Unmarshal(jsonByte, &tableData); e != nil {
+				err = fmt.Errorf("解析自定义表格数据失败, Err: %s", e.Error())
+				return
+			}
+			for _, tv := range tableData.EdbInfoIdList {
+				if edbIdExist[tv] {
+					continue
+				}
+				edbIdExist[tv] = true
+				edbIds = append(edbIds, tv)
+			}
+		}
+
+		// 混合表格
+		if detail.Source == utils.MIXED_TABLE {
+			jsonByte, e := json.Marshal(detail.TableData)
+			if e != nil {
+				err = fmt.Errorf("JSON格式化混合表格数据失败, Err: %s", e.Error())
+				return
+			}
+			var tableData request.MixedTableReq
+			if e = json.Unmarshal(jsonByte, &tableData); e != nil {
+				err = fmt.Errorf("解析混合表格数据失败, Err: %s", e.Error())
+				return
+			}
+			if len(tableData.Data) > 0 {
+				for _, td := range tableData.Data {
+					for _, tv := range td {
+						if tv.EdbInfoId > 0 && !edbIdExist[tv.EdbInfoId] {
+							edbIdExist[tv.EdbInfoId] = true
+							edbIds = append(edbIds, tv.EdbInfoId)
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// GetExcelEdbBatchRefreshKey 获取批量刷新表格指标缓存key
+func GetExcelEdbBatchRefreshKey(source string, reportId, chapterId int) string {
+	if source == `` {
+		return ``
+	}
+
+	return fmt.Sprint("batch_refresh_excel_edb:", source, ":", reportId, ":", chapterId)
+}

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

@@ -0,0 +1,286 @@
+package excel
+
+import (
+	"errors"
+	excelModel "eta/eta_mobile/models/data_manage/excel"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/services"
+	"eta/eta_mobile/services/alarm_msg"
+	excel "eta/eta_mobile/services/excel"
+	"eta/eta_mobile/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
+	//}
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		err = fmt.Errorf("初始化OSS服务失败")
+		return
+	}
+	resourceUrl, err = ossClient.UploadFile(fileName, downloadFilePath, "")
+	if err != nil {
+		err = fmt.Errorf("文件上传失败, Err: %s", err.Error())
+		return
+	}
+
+	excelInfo.FileUrl = resourceUrl
+	err = excelInfo.Update([]string{"FileUrl"})
+}

+ 1301 - 0
services/data/excel/mixed_table.go

@@ -0,0 +1,1301 @@
+package excel
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/models/data_manage/excel/request"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// BaseCalculate
+// @Description: 指标数据计算请求
+type BaseCalculate struct {
+	DataList      []*data_manage.EdbDataList
+	Frequency     string `description:"需要转换的频度"`
+	Formula       interface{}
+	Calendar      string `description:"公历/农历"`
+	MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
+	MoveFrequency string `description:"移动频度"`
+	FromFrequency string `description:"来源的频度"`
+	Source        int    `description:"1:累计值转月;2:累计值转季;3:同比值;4:同差值;5:N数值移动平均数计算;6:环比值;7:环差值;8:升频;9:降频;10:时间移位;11:超季节性;12:年化;13:累计值;14:累计值年初至今;15:指数修匀;16:日均值"`
+}
+
+// Cell
+// @Description: 单元格位置
+type Cell struct {
+	Column   int                           `description:"行"`
+	Row      int                           `description:"列"`
+	CellInfo request.MixedTableCellDataReq `description:"对应的单元格信息"`
+}
+
+// GetMixedTableCellData 获取混合表格数据
+func GetMixedTableCellData(mixedTableReq request.MixedTableReq) (newMixedTableCellDataList [][]request.MixedTableCellDataReq, err error, errMsg string) {
+	cellRelationConf := mixedTableReq.CellRelation
+	config := mixedTableReq.Data
+
+	// 单元格关系配置x信息
+	cellRelationConfMap := make(map[string]request.CellRelationConf)
+	cellRelationConfList := make([]request.CellRelationConf, 0)
+	if cellRelationConf != `` {
+		err = json.Unmarshal([]byte(cellRelationConf), &cellRelationConfList)
+		if err != nil {
+			return
+		}
+
+		for _, v := range cellRelationConfList {
+			cellRelationConfMap[v.Key] = v
+		}
+	}
+
+	// 找出所有的关联指标id
+	config, edbInfoIdList, _, err, errMsg := handleConfig(config)
+	if err != nil {
+		return
+	}
+
+	// 查询所有关联的指标信息
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(edbInfoIdList)
+	if err != nil {
+		return
+	}
+
+	// 指标信息map
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	// 日度指标数据map
+	edbDayDataListMap := make(map[int]map[string]float64)
+	// 月度指标数据map
+	edbMonthDataListMap := make(map[int]map[string]string)
+	// 日度指标数据map
+	edbDataListMap := make(map[int][]*data_manage.EdbDataList)
+	for _, edbInfo := range edbInfoList {
+		edbInfoMap[edbInfo.EdbInfoId] = edbInfo
+
+		dataList := make([]*data_manage.EdbDataList, 0)
+		switch edbInfo.EdbInfoType {
+		case 0:
+			dataList, _ = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+		case 1:
+			_, dataList, _, _, _, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+		default:
+			err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+		}
+
+		dateValMap := make(map[string]float64)
+		monthDateMap := make(map[string]string)
+		for _, tmpData := range dataList {
+			// 日度数据
+			dateValMap[tmpData.DataTime] = tmpData.Value
+			// 月度数据(取该月份的第一个数据)
+			yearMonth := strings.Join(strings.Split(tmpData.DataTime, "-")[0:2], "-")
+			if _, ok := monthDateMap[yearMonth]; !ok {
+				// 存最早的时间
+				monthDateMap[yearMonth] = tmpData.DataTime
+			}
+		}
+		edbDayDataListMap[edbInfo.EdbInfoId] = dateValMap
+		edbMonthDataListMap[edbInfo.EdbInfoId] = monthDateMap
+		edbDataListMap[edbInfo.EdbInfoId] = dataList
+	}
+
+	// 单元格实际绑定的信息map
+	cellDataRelationMap := make(map[string]request.MixedTableCellDataReq, 0)
+
+	// 处理指定指标的日期
+	for k, row := range config {
+		for i, cell := range row {
+			// 单元格是日期类型,且是日导入指标日期(指标库的最新日期)
+			if cell.DataType == request.DateDT && cell.DataTimeType == request.EdbDateDT {
+				// 指标id是在配置里面
+				var edbDateConfig request.EdbDateConf
+				err = json.Unmarshal([]byte(cell.Value), &edbDateConfig)
+				if err != nil {
+					return
+				}
+				if dataList, ok := edbDataListMap[edbDateConfig.EdbInfoId]; ok {
+					// todo 获取配置信息,根据配置信息进行日期变换, 是否需要更新当前记录,将历史记录逐渐转换成新的记录
+					var newDate string
+					newDate, err = GetEdbDateByMoveForward(config[k][i].Value, dataList)
+					if err != nil {
+						return
+					}
+					newDate, err = HandleMixTableDateChange(newDate, config[k][i].Value)
+					if err != nil {
+						return
+					}
+					cell.ShowValue = newDate
+					cell.DataTime = newDate
+					config[k][i] = cell
+				}
+			}
+			row[i] = cell
+
+			cellDataRelationMap[cell.Uid] = cell
+		}
+		config[k] = row
+	}
+
+	// 指标计算的结果map
+	edbSourceDataMap := make(map[string]data.BaseCalculateDataResp)
+
+	// 单元格对应的key与他的值(只处理数据类型)
+	cellKeyVal := make(map[string]float64)
+
+	// 基础计算单元格的位置信息
+	calculateCellMap := make(map[string]Cell)
+	calculateChainList := make([]string, 0)
+	dateCalculateList := make([]string, 0)
+	showStyleList := make([]string, 0)
+
+	// 处理单元格中的数据类型(除去基础计算,因为这个是依赖于其他)
+	for k, row := range config {
+		for i, cell := range row {
+			switch cell.DataType {
+			case request.EdbDT: // 指标类型
+				if cell.Value == `` {
+					if edbInfo, ok := edbInfoMap[cell.EdbInfoId]; ok {
+						cell.ShowValue = edbInfo.EdbName
+					}
+				} else {
+					cell.ShowValue = cell.Value
+				}
+			case request.InsertDataDT, request.PopInsertDataDT: // 数据类型
+				// 数值先清空
+				cell.ShowValue = ``
+				//cell.Value = ``
+
+				// 日期关系配置不存在,则默认最新数据
+				if relationConf, ok := cellRelationConfMap[cell.Uid]; ok { //表示表格日期
+					if relationConf.RelationDate.Key == `` {
+						// 日期关系配置未绑定
+						continue
+					}
+					// 配置
+					relationCell, ok := cellDataRelationMap[relationConf.RelationDate.Key]
+					if !ok {
+						// 找不到对应日期的单元格
+						continue
+					}
+
+					// 确实是找到了这个关联日期的单元格,那么通过日期重新获取数据值
+					tmpDateValMap := make(map[string]float64)
+					// 日度数据
+					if dateValMap, ok := edbDayDataListMap[cell.EdbInfoId]; ok {
+						tmpDateValMap = dateValMap
+					}
+					// todo 根据配置进行日期变换
+					relationDate := relationCell.DataTime
+					if strings.Contains(cell.Value, "{") {
+						relationDate, err = HandleMixTableDateChange(relationDate, cell.Value)
+						if err != nil {
+							return
+						}
+					} else {
+						cell.Value = ""
+					}
+
+					if val, ok2 := tmpDateValMap[relationDate]; ok2 {
+						//cell.ShowValue = fmt.Sprint(val)
+						cellKeyVal[cell.Uid] = val
+						cell.ShowValue = utils.FormatMixTableDataShowValue(val)
+					}
+				} else {
+					// 如果不是取得一个关联的日期,那么就是指定日期
+					// 如果没有指定日期,则默认最新数据
+					if cell.DataTime == `` {
+						// 指标的最新日期
+						if dateValList, ok := edbDataListMap[cell.EdbInfoId]; ok {
+							tmpLenData := len(dateValList)
+							if tmpLenData > 0 {
+								//做期数前移动和日期变换
+								if !strings.Contains(cell.Value, "{") {
+									cell.Value = ""
+								}
+								var newDate string
+								newDate, err = GetEdbDateByMoveForward(cell.Value, dateValList)
+								if err != nil {
+									return
+								}
+								newDate, err = HandleMixTableDateChange(newDate, cell.Value)
+								if err != nil {
+									return
+								}
+								var finalVal string
+								for _, v := range dateValList {
+									if v.DataTime == newDate {
+										finalVal = utils.FormatMixTableDataShowValue(v.Value)
+										cellKeyVal[cell.Uid] = v.Value
+										break
+									}
+								}
+
+								cell.ShowValue = finalVal
+							}
+						}
+					} else {
+						tmpDateList := strings.Split(cell.DataTime, "-")
+						tmpDateValMap := make(map[string]float64)
+						var newDate string
+						if len(tmpDateList) == 2 {
+							//月度数据
+							if dateMap, ok1 := edbMonthDataListMap[cell.EdbInfoId]; ok1 {
+								if d, ok2 := dateMap[cell.DataTime]; ok2 {
+									newDate = d
+								}
+							}
+						} else {
+							// 日度数据
+							newDate = cell.DataTime
+						}
+						// 日期变换后才能确定最后的时间
+						//做期数前移动和日期变换
+						if !strings.Contains(cell.Value, "{") {
+							cell.Value = ""
+						}
+
+						newDate, err = HandleMixTableDateChange(newDate, cell.Value)
+						if err != nil {
+							return
+						}
+
+						if dateValMap, ok3 := edbDayDataListMap[cell.EdbInfoId]; ok3 {
+							tmpDateValMap = dateValMap
+							if val, ok2 := tmpDateValMap[cell.DataTime]; ok2 {
+								//cell.ShowValue = fmt.Sprint(val)
+								cellKeyVal[cell.Uid] = val
+								cell.ShowValue = utils.FormatMixTableDataShowValue(val)
+							}
+						}
+					}
+				}
+			case request.CustomTextDT: //自定义文本
+				if cell.Value == `` {
+					continue
+				}
+				// 处理看下能否转成float,如果可以的话,说明这个也是可以参与计算的
+				tmpDeci, tmpErr := decimal.NewFromString(cell.Value)
+				if tmpErr == nil {
+					tmpVal, _ := tmpDeci.Float64()
+					cellKeyVal[cell.Uid] = tmpVal
+				}
+
+			case request.FormulateCalculateDataDT: // 公式计算(A+B这种)
+				calculateChainList = append(calculateChainList, cell.Uid)
+
+			case request.InsertEdbCalculateDataDT: // 插入指标系统计算公式生成的值
+				// 处理value的值
+				if !strings.Contains(cell.Value, "EdbInfoId") && cell.EdbInfoId > 0 {
+					cell.Value, _ = fixCalculateValueConfig(cell.Value, cell.EdbInfoId)
+					row[i] = cell
+				}
+				// 日期
+				var cellDateTime string
+				// 日期关系配置不存在,则默认最新数据
+				// 从绑定的单元格中获取数据的日期
+				if relationConf, ok := cellRelationConfMap[cell.Uid]; ok {
+					if relationConf.RelationDate.Key == `` {
+						// 日期关系配置未绑定
+						continue
+					}
+					// 配置
+					relationCell, ok := cellDataRelationMap[relationConf.RelationDate.Key]
+					if !ok {
+						// 找不到对应日期的单元格
+						continue
+					}
+					cellDateTime = relationCell.DataTime
+				}
+
+				var tmpDataMap map[string]float64
+
+				key := utils.MD5(cell.Value)
+				respItemData, ok := edbSourceDataMap[key]
+				if !ok {
+					// 对应的配置值
+					var tmpConfig request.CalculateConf
+					err = json.Unmarshal([]byte(cell.Value), &tmpConfig)
+					if err != nil {
+						return
+					}
+
+					tmpDataList, ok := edbDataListMap[tmpConfig.EdbInfoId]
+					if !ok {
+						continue
+					}
+					edbInfo, ok := edbInfoMap[tmpConfig.EdbInfoId]
+					if !ok {
+						continue
+					}
+
+					req2 := &BaseCalculate{
+						DataList:      tmpDataList,
+						Frequency:     tmpConfig.Frequency,
+						Formula:       tmpConfig.Formula,
+						Calendar:      tmpConfig.Calendar,
+						MoveType:      tmpConfig.MoveType,
+						MoveFrequency: tmpConfig.MoveFrequency,
+						FromFrequency: edbInfo.Frequency,
+						Source:        tmpConfig.Source,
+					}
+
+					// 调用指标库去更新
+					reqJson, tmpErr := json.Marshal(req2)
+					if tmpErr != nil {
+						utils.FileLog.Error(fmt.Sprintf("计算失败1,配置信息;%s;错误原因:%s", cell.Value, tmpErr.Error()))
+						err = tmpErr
+						return
+					}
+					respItem, tmpErr := data.BaseCalculate(string(reqJson))
+					if tmpErr != nil {
+						utils.FileLog.Error(fmt.Sprintf("计算失败2,配置信息;%s;错误原因:%s", cell.Value, tmpErr.Error()))
+						err = tmpErr
+						return
+					}
+					if respItem.Ret != 200 {
+						utils.FileLog.Error(fmt.Sprintf("计算失败3,配置信息;%s;原因:%s;错误原因:%s", cell.Value, respItem.Msg, respItem.ErrMsg))
+						continue
+					}
+
+					//tmpDataMap = respItem.Data.DataMap
+					// 计算结果存一份,万一存在重复的计算方式,那么省的重新计算一下
+					edbSourceDataMap[key] = respItem.Data
+					respItemData = respItem.Data
+				}
+				lenDataList := len(respItemData.DateList)
+				tmpDataMap = respItemData.DataMap
+				if cellDateTime == `` && lenDataList > 0 {
+					//判断是否需要做期数前移动
+					cellDateTime = respItemData.DateList[lenDataList-1]
+					cellDateTime, err = GetEdbDateByMoveForwardByDateList(cell.Value, respItemData.DateList)
+					if err != nil {
+						utils.FileLog.Error(fmt.Sprintf("日期前移失败,配置信息;%s", cell.Value))
+						continue
+					}
+				}
+				// 进行日期变换
+				cellDateTime, err = HandleMixTableDateChange(cellDateTime, cell.Value)
+				if err != nil {
+					utils.FileLog.Error(fmt.Sprintf("日期变换失败,配置信息;%s, 日期:%s", cell.Value, cellDateTime))
+					continue
+				}
+				val, ok := tmpDataMap[cellDateTime]
+				if ok {
+					cellKeyVal[cell.Uid] = val
+					cell.ShowValue = utils.FormatMixTableDataShowValue(val)
+				} else {
+					cell.ShowValue = ""
+				}
+
+			case request.DateCalculateDataDT: //日期计算
+				dateCalculateList = append(dateCalculateList, cell.Uid)
+				// 遍历数组,根据公式进行计算,并将得到的结果放到对应的单元格中
+			}
+			row[i] = cell
+		}
+		config[k] = row
+	}
+
+	// 处理指定指标的日期
+	for k, row := range config {
+		for i, cell := range row {
+			calculateCellMap[cell.Uid] = Cell{
+				Column:   k,
+				Row:      i,
+				CellInfo: cell,
+			}
+			cell.ShowFormatValue = cell.ShowValue
+			if cell.ShowStyle != `` {
+				showStyleList = append(showStyleList, cell.Uid)
+			}
+			row[i] = cell
+			cellDataRelationMap[cell.Uid] = cell
+		}
+		config[k] = row
+	}
+
+	// 公式链计算
+	if len(calculateChainList) > 0 {
+		for _, cellKey := range calculateChainList {
+			// 查找这个单元格的位置,直接map找了,而不是遍历整个单元格
+			cellPosition, ok := calculateCellMap[cellKey]
+			if !ok {
+				utils.FileLog.Error("找不到单元格位置:", cellKey)
+				continue
+			}
+
+			cell := config[cellPosition.Column][cellPosition.Row]
+			if cell.DataType != request.FormulateCalculateDataDT { // 判断公式计算(A+B这种)类型,不是的话也过滤了
+				continue
+			}
+
+			val, has, tmpErr, tmpErrMsg := getCalculateValueByCell(calculateCellMap, cellKey, cellKeyVal)
+			if tmpErr != nil {
+				errMsg = tmpErrMsg
+				err = tmpErr
+				return
+			}
+			if !has {
+				continue
+			}
+
+			cellKeyVal[cell.Uid] = val
+			cell.ShowValue = utils.FormatMixTableDataShowValue(val)
+			config[cellPosition.Column][cellPosition.Row] = cell
+
+		}
+	}
+
+	// 日期计算
+	config, err, errMsg = handlerDateCalculate(dateCalculateList, calculateCellMap, config)
+	if err != nil {
+		return
+	}
+
+	// 格式化展示
+	config, err, errMsg = handleMixCellShowStyle(showStyleList, calculateCellMap, config)
+	if err != nil {
+		return
+	}
+
+	newMixedTableCellDataList = config
+
+	return
+}
+
+// getCalculateValue 获取公式计算的结果
+func getCalculateValueByCell(calculateCellMap map[string]Cell, key string, cellKeyValMap map[string]float64) (val float64, has bool, err error, errMsg string) {
+	// 单元格的标签名
+	val, ok := cellKeyValMap[key]
+	if ok {
+		has = true
+		return
+	}
+
+	// 查找单元格数据
+	cell, ok := calculateCellMap[key]
+	if !ok {
+		err = errors.New("查找单元格" + key + "的数据失败")
+		return
+	}
+
+	colData := cell.CellInfo
+
+	// 如果不是基础计算单元格,直接返回
+	if colData.DataType != request.FormulateCalculateDataDT {
+		return
+	}
+
+	// 如果是计算单元格
+
+	tagList := make([]utils.CellPosition, 0)
+	// 计算单元格relationCellList
+	var relationCellList []request.RelationCell
+	if colData.Extra == `` {
+		err = errors.New(colData.Uid + "没有绑定关系")
+		return
+	}
+	err = json.Unmarshal([]byte(colData.Extra), &relationCellList)
+	if err != nil {
+		return
+	}
+
+	for _, relation := range relationCellList {
+		//relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row
+		tmpVal, _, tmpErr, tmpErrMsg := getCalculateValueByCell(calculateCellMap, relation.Key, cellKeyValMap)
+		if tmpErr != nil {
+			errMsg = tmpErrMsg
+			err = tmpErr
+			return
+		}
+		cellKeyValMap[relation.Key] = tmpVal
+
+		rowInt, tmpErr := strconv.Atoi(relation.Row)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		tagList = append(tagList, utils.CellPosition{
+			Tag:   relation.Tag,
+			Row:   rowInt,
+			Value: tmpVal,
+		})
+	}
+
+	// 计算
+	val, errMsg, err = calculateByCellList(strings.ToUpper(colData.Value), tagList)
+	if err != nil {
+		return
+	}
+	// 重新赋值
+	has = true
+	cellKeyValMap[key] = val
+
+	return
+}
+
+// handleConfig
+// @Description: 处理混合表格配置
+// @author: Roc
+// @datetime2023-10-27 13:24:53
+// @param configList [][]request.MixedTableCellDataReq
+// @return newConfig [][]request.MixedTableCellDataReq
+// @return edbInfoIdList []int
+// @return dataEdbInfoIdList []int
+// @return err error
+// @return errMsg string
+func handleConfig(configList [][]request.MixedTableCellDataReq) (newConfig [][]request.MixedTableCellDataReq, edbInfoIdList []int, dataEdbInfoIdList []int, err error, errMsg string) {
+	edbInfoIdList = make([]int, 0)
+	dataEdbInfoIdList = make([]int, 0)
+
+	for ck, rowList := range configList {
+		for rk, cell := range rowList {
+			switch cell.DataType {
+			case request.EdbDT: // 指标信息
+				edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId)
+			case request.InsertDataDT, request.PopInsertDataDT: // 插值、弹框插值
+				dataEdbInfoIdList = append(dataEdbInfoIdList, cell.EdbInfoId)
+				edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId)
+			case request.InsertEdbCalculateDataDT: // 插入指标计算公式生成的值
+				var config request.CalculateConf
+				err = json.Unmarshal([]byte(cell.Value), &config)
+				if err != nil {
+					return
+				}
+				if config.EdbInfoId == 0 && cell.EdbInfoId > 0 {
+					edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId)
+				} else {
+					edbInfoIdList = append(edbInfoIdList, config.EdbInfoId)
+				}
+				dataEdbInfoIdList = append(dataEdbInfoIdList, cell.EdbInfoId)
+
+			case request.DateDT: // 日期类型
+				date, newVal, tmpErr, tmpErrMsg := handleDate(cell.DataTimeType, cell.Value)
+				if tmpErr != nil {
+					err = tmpErr
+					errMsg = tmpErrMsg
+					return
+				}
+				rowList[rk].DataTime = date
+				rowList[rk].ShowValue = date
+				rowList[rk].Value = newVal //兼容原有的历史数据中系统导入日期
+
+				// 指标日期类型的单元格需要额外将指标id取出来
+				if cell.DataTimeType == request.EdbDateDT {
+					var config request.EdbDateConf
+					err = json.Unmarshal([]byte(cell.Value), &config)
+					if err != nil {
+						return
+					}
+					edbInfoIdList = append(edbInfoIdList, config.EdbInfoId)
+				}
+			}
+		}
+		configList[ck] = rowList
+	}
+
+	newConfig = configList
+
+	return
+}
+
+// HandleDate
+// @Description: 日期处理
+// @author: Roc
+// @datetime2023-10-27 09:37:02
+// @param dataTimeType int
+// @param val string
+// @return date string
+// @return err error
+// @return errMsg string
+func HandleDate(dataTimeType int, val string) (date string, newVal string, err error, errMsg string) {
+	return handleDate(dataTimeType, val)
+}
+
+// handleDate
+// @Description: 日期处理
+// @author: Roc
+// @datetime2023-10-27 09:36:49
+// @param dataTimeType int
+// @param val string
+// @return date string
+// @return err error
+// @return errMsg string
+func handleDate(dataTimeType int, val string) (date string, newVal string, err error, errMsg string) {
+	newVal = val
+	if val == `` {
+		errMsg = "错误的日期数据"
+		err = errors.New(errMsg)
+		return
+	}
+
+	switch dataTimeType {
+	case request.CustomDateT: //手动输入日期
+		/*if !strings.Contains(val, "{") {
+			newVal, err, errMsg = handleOldCustomerDateT(val)
+			if err != nil {
+				return
+			}
+		}
+		date, err = HandleMixTableDateChange("", newVal)
+		if err != nil {
+			return
+		}*/
+		date = val
+		return
+	case request.SystemDateT: // 系统日期
+		date = time.Now().Format(utils.FormatDate)
+		newVal, err, errMsg = handleOldSystemDateT(val)
+		if err != nil {
+			return
+		}
+		date, err = HandleMixTableDateChange(date, newVal)
+		if err != nil {
+			return
+		}
+
+	case request.EdbDateDT: // 导入指标日期(指标库的最新日期)
+	default:
+		errMsg = "错误的日期类型"
+		err = errors.New(errMsg)
+		return
+	}
+
+	return
+}
+
+func GetEdbDateByMoveForward(conf string, edbDataList []*data_manage.EdbDataList) (date string, err error) {
+	dateList := make([]string, 0)
+	for _, v := range edbDataList {
+		dateList = append(dateList, v.DataTime)
+	}
+
+	date, err = GetEdbDateByMoveForwardByDateList(conf, dateList)
+	return
+}
+
+func GetEdbDateByMoveForwardByDateList(conf string, dateList []string) (date string, err error) {
+	moveForward := 0
+	if conf != "" {
+		var edbDateConf request.EdbDateChangeConf
+		err = json.Unmarshal([]byte(conf), &edbDateConf)
+		if err != nil {
+			err = fmt.Errorf("日期变换配置json解析失败失败: %s", err.Error())
+			return
+		}
+		moveForward = edbDateConf.MoveForward
+	}
+	// 根据日期进行排序
+	index := len(dateList) - 1 - moveForward
+	for k, v := range dateList {
+		if k == index {
+			date = v
+			return
+		}
+	}
+	return
+}
+
+// HandleMixTableDateChange 处理表格中的日期变换
+func HandleMixTableDateChange(date, conf string) (newDate string, err error) {
+	newDate = date
+	if conf == "" {
+		return
+	}
+	var edbDateConf request.EdbDateConf
+	err = json.Unmarshal([]byte(conf), &edbDateConf)
+	if err != nil {
+		err = fmt.Errorf("日期变换配置json解析失败失败: %s, Err:%s", conf, err.Error())
+		return
+	}
+
+	if newDate != "" {
+		if len(edbDateConf.DateChange) > 0 {
+			var dateTime time.Time
+			dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+			if err != nil {
+				err = fmt.Errorf("日期解析失败: %s", err.Error())
+				return
+			}
+			for _, v := range edbDateConf.DateChange {
+				if v.ChangeType == 1 {
+					dateTime = dateTime.AddDate(v.Year, v.Month, v.Day)
+					newDate = dateTime.Format(utils.FormatDate)
+				} else if v.ChangeType == 2 {
+					newDate, err, _ = handleSystemAppointDateT(dateTime, v.FrequencyDay, v.Frequency)
+					if err != nil {
+						return
+					}
+					dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+					if err != nil {
+						err = fmt.Errorf("日期解析失败: %s", err.Error())
+						return
+					}
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// handleOldSystemDateT
+// @Description: 历史数据中的导入系统日期
+// @author: Roc
+// @datetime2023-10-27 09:36:21
+// @param confStr string
+// @return date string
+// @return err error
+// @return errMsg string
+func handleOldSystemDateT(confStr string) (newConf string, err error, errMsg string) {
+	newConf = confStr
+	var config request.SystemDateConf
+	err = json.Unmarshal([]byte(confStr), &config)
+	if err != nil {
+		return
+	}
+
+	newConfig := new(request.EdbDateConf)
+	dateChange := new(request.EdbDateConfDateChange)
+
+	dateChangeList := make([]*request.EdbDateConfDateChange, 0)
+
+	switch config.Source {
+	case request.SystemCurrDateT:
+		return
+	case request.SystemCalculateDateT:
+		// todo 是否直接更新该excel记录,
+		dateChange.Day = config.CalculateNum
+		dateChange.ChangeType = 1
+		dateChangeList = append(dateChangeList, dateChange)
+		newConfig.DateChange = dateChangeList
+		newConfByte, e := json.Marshal(newConfig)
+		if e != nil {
+			err = fmt.Errorf("日期计算额外配置,json序列化失败: %s", e.Error())
+			return
+		}
+		newConf = string(newConfByte)
+		return
+	case request.SystemFrequencyDateT: // 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天)
+		dateChange.FrequencyDay = config.Day
+		dateChange.Frequency = config.Frequency
+		dateChange.ChangeType = 1
+		dateChangeList = append(dateChangeList, dateChange)
+		newConfig.DateChange = dateChangeList
+		newConfByte, e := json.Marshal(newConfig)
+		if e != nil {
+			err = fmt.Errorf("日期计算额外配置,json序列化失败: %s", e.Error())
+			return
+		}
+		newConf = string(newConfByte)
+		return
+	default:
+		//errMsg = "错误的日期日期导入方式"
+		//err = errors.New(fmt.Sprint("错误的日期日期导入方式:", config.Source))
+		return
+	}
+
+	return
+}
+
+// handleSystemCalculateDateT
+// @Description: 处理系统日期计算后的日期
+// @author: Roc
+// @datetime2023-10-27 09:31:22
+// @param num int
+// @param frequency string
+// @return date string
+// @return err error
+// @return errMsg string
+func handleSystemCalculateDateT(num int, frequency string) (date string, err error, errMsg string) {
+	if err != nil {
+		return
+	}
+	currDate := time.Now()
+	switch frequency {
+	case "", "日":
+		date = currDate.AddDate(0, 0, num).Format(utils.FormatDate)
+	default:
+		errMsg = "错误的日期频度:" + frequency
+		err = errors.New(errMsg)
+		return
+	}
+
+	return
+}
+
+// handleSystemAppointDateT
+// @Description: 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天)
+// @author: Roc
+// @datetime2023-10-27 09:31:35
+// @param Frequency string
+// @param Day string
+// @return date string
+// @return err error
+// @return errMsg string
+func handleSystemAppointDateT(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
+	//currDate := time.Now()
+	switch frequency {
+	case "本周":
+		day := int(currDate.Weekday())
+		if day == 0 { // 周日
+			day = 7
+		}
+		num := 0
+		switch appointDay {
+		case "周一":
+			num = 1
+		case "周二":
+			num = 2
+		case "周三":
+			num = 3
+		case "周四":
+			num = 4
+		case "周五":
+			num = 5
+		case "周六":
+			num = 6
+		case "周日":
+			num = 7
+		}
+		day = num - day
+		date = currDate.AddDate(0, 0, day).Format(utils.FormatDate)
+	case "本旬":
+		day := currDate.Day()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if day <= 10 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location())
+			} else if day <= 20 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 11, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 21, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if day <= 10 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 10, 0, 0, 0, 0, currDate.Location())
+			} else if day <= 20 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 20, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1)
+			}
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本月":
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location())
+		case "最后一天":
+			tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1)
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本季":
+		month := currDate.Month()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if month <= 3 {
+				tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 4, 1, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 9 {
+				tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 10, 1, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if month <= 3 {
+				tmpDate = time.Date(currDate.Year(), 3, 31, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 9 {
+				tmpDate = time.Date(currDate.Year(), 9, 30, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+			}
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本半年":
+		month := currDate.Month()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+			}
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本年":
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+		case "最后一天":
+			tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	default:
+		errMsg = "错误的日期频度:" + frequency
+		err = errors.New(errMsg)
+		return
+	}
+
+	return
+}
+
+// calculateByCellList
+// @Description: 根据单元格来进行公式计算
+// @author: Roc
+// @datetime2023-11-14 16:17:38
+// @param calculateFormula string
+// @param tagList []utils.CellPosition
+// @return calVal string
+// @return errMsg string
+// @return err error
+func calculateByCellList(calculateFormula string, tagList []utils.CellPosition) (calVal float64, errMsg string, err error) {
+	if calculateFormula == "" {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	calculateFormula = strings.TrimPrefix(calculateFormula, "=")
+	calculateFormula = strings.Replace(calculateFormula, "(", "(", -1)
+	calculateFormula = strings.Replace(calculateFormula, ")", ")", -1)
+	calculateFormula = strings.Replace(calculateFormula, ",", ",", -1)
+	calculateFormula = strings.Replace(calculateFormula, "。", ".", -1)
+	calculateFormula = strings.Replace(calculateFormula, "%", "*0.01", -1)
+
+	rowList := make([]int, 0)
+	rowListMap := make(map[int][]utils.CellPosition)
+	for _, v := range tagList {
+		tmpRowList, ok := rowListMap[v.Row]
+		if !ok {
+			rowList = append(rowList, v.Row)
+			tmpRowList = make([]utils.CellPosition, 0)
+		}
+		tmpRowList = append(tmpRowList, v)
+		rowListMap[v.Row] = tmpRowList
+	}
+
+	sort.Ints(rowList)
+
+	list := make([]utils.CellPosition, 0)
+	for _, row := range rowList {
+		list = append(list, rowListMap[row]...)
+	}
+
+	formulaFormStr := utils.ReplaceFormulaByCellList(list, calculateFormula)
+	//计算公式异常,那么就移除该指标
+	if formulaFormStr == `` {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	expression := formula.NewExpression(formulaFormStr)
+	calResult, err := expression.Evaluate()
+	if err != nil {
+		errMsg = "计算失败"
+		err = errors.New("计算失败:Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
+		// 分母为0的报错
+		if strings.Contains(err.Error(), "divide by zero") {
+			errMsg = "分母不能为0"
+			err = errors.New("分母不能为空,计算公式:" + formulaFormStr)
+		}
+		return
+	}
+	// 如果计算结果是NAN,那么就提示报错
+	if calResult.IsNan() {
+		errMsg = "计算失败"
+		err = errors.New("计算失败:计算结果是:NAN;formulaStr:" + formulaFormStr)
+		return
+	}
+	calVal, err = calResult.Float64()
+	if err != nil {
+		return
+	}
+
+	// 转Decimal然后四舍五入
+	calVal, _ = decimal.NewFromFloat(calVal).Round(4).Float64()
+
+	return
+}
+
+// handlerDateCalculate 处理日期计算
+func handlerDateCalculate(dateCalculateList []string, calculateCellMap map[string]Cell, oldConfig [][]request.MixedTableCellDataReq) (config [][]request.MixedTableCellDataReq, err error, errMsg string) {
+	config = oldConfig
+	if len(dateCalculateList) == 0 {
+		return
+	}
+	if len(dateCalculateList) > 0 {
+		for _, cellKey := range dateCalculateList {
+			// 查找这个单元格的位置,直接map找了,而不是遍历整个单元格
+			cellPosition, ok := calculateCellMap[cellKey]
+			if !ok {
+				utils.FileLog.Error("找不到单元格位置:", cellKey)
+				continue
+			}
+
+			cell := config[cellPosition.Column][cellPosition.Row]
+			if cell.DataType != request.DateCalculateDataDT { // 判断公式计算(A+B这种)类型,不是的话也过滤了
+				continue
+			}
+
+			val, tmpErr, tmpErrMsg := DateCalculatePrepare(calculateCellMap, cell.Value)
+			if tmpErr != nil {
+				cell.ShowValue = ""
+				config[cellPosition.Column][cellPosition.Row] = cell
+				utils.FileLog.Error(fmt.Sprintf("%s 日期计算报错:Err:%s:%s", cellKey, tmpErr, tmpErrMsg))
+				continue
+			}
+
+			cell.ShowValue = utils.FormatMixTableDataShowValue(val)
+			config[cellPosition.Column][cellPosition.Row] = cell
+		}
+	}
+	return
+}
+
+// DateCalculatePrepare 单个单元格的日期计算
+func DateCalculatePrepare(calculateCellMap map[string]Cell, config string) (val float64, err error, errMsg string) {
+	var edbDateConf request.MixDateCalculateConf
+	err = json.Unmarshal([]byte(config), &edbDateConf)
+	if err != nil {
+		err = fmt.Errorf("日期计算配置json解析失败失败: %s, Err:%s", config, err.Error())
+		return
+	}
+	if len(edbDateConf.RelationCellList) == 0 {
+		err = fmt.Errorf("日期计算 未配置日期单元格失败: %s", config)
+		return
+	}
+	valMap := make(map[string]int)
+	for _, v := range edbDateConf.RelationCellList {
+		// 查找单元格数据
+		cell, ok := calculateCellMap[v.Uid]
+		if !ok {
+			err = fmt.Errorf("查找单元格:%s 的数据失败", v.Uid)
+			return
+		}
+		colData := cell.CellInfo
+
+		// 如果不是基础计算单元格,直接返回
+		_, err = time.ParseInLocation(utils.FormatDate, colData.ShowValue, time.Local)
+		if err != nil {
+			err = fmt.Errorf("%s 的单元格非日期类型, Err: %s", colData.ShowValue, err.Error())
+			return
+		}
+		// todo 把日期转换成excel里的天数
+		realDiffDay := utils.GetDaysDiff1900(colData.ShowValue)
+
+		valMap[strings.ToUpper(v.Tag)] = realDiffDay
+	}
+
+	// 计算
+	val, errMsg, err = DateCalculateFormula(valMap, strings.ToUpper(edbDateConf.Formula))
+	if err != nil {
+		return
+	}
+	return
+}
+
+func DateCalculateFormula(valTagMap map[string]int, calculateFormula string) (calVal float64, errMsg string, err error) {
+	if calculateFormula == "" {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	calculateFormula = strings.TrimPrefix(calculateFormula, "=")
+	calculateFormula = strings.Replace(calculateFormula, "(", "(", -1)
+	calculateFormula = strings.Replace(calculateFormula, ")", ")", -1)
+	calculateFormula = strings.Replace(calculateFormula, ",", ",", -1)
+	calculateFormula = strings.Replace(calculateFormula, "。", ".", -1)
+	calculateFormula = strings.Replace(calculateFormula, "%", "*0.01", -1)
+
+	formulaFormStr := utils.ReplaceFormulaByTagMap(valTagMap, calculateFormula)
+
+	if formulaFormStr == `` {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+	fmt.Println("公式:" + formulaFormStr)
+	expression := formula.NewExpression(formulaFormStr)
+	calResult, err := expression.Evaluate()
+	if err != nil {
+		errMsg = "公式错误,请重新填写"
+		err = errors.New("计算失败:Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
+		// 分母为0的报错
+		if strings.Contains(err.Error(), "divide by zero") {
+			errMsg = "分母不能为0"
+			err = errors.New("分母不能为空,计算公式:" + formulaFormStr)
+		}
+		return
+	}
+	// 如果计算结果是NAN,那么就提示报错
+	if calResult.IsNan() {
+		errMsg = "公式错误,请重新填写"
+		err = errors.New("计算失败:计算结果是:NAN;formulaStr:" + formulaFormStr)
+		return
+	}
+	calVal, err = calResult.Float64()
+	if err != nil {
+		return
+	}
+
+	// 转Decimal然后四舍五入
+	calVal, _ = decimal.NewFromFloat(calVal).Round(4).Float64()
+
+	return
+}
+
+// handleMixCellShowStyle 处理混合表格中,显示计算的逻辑
+func handleMixCellShowStyle(showStyleList []string, calculateCellMap map[string]Cell, oldConfig [][]request.MixedTableCellDataReq) (config [][]request.MixedTableCellDataReq, err error, errMsg string) {
+	config = oldConfig
+	if len(showStyleList) == 0 {
+		return
+	}
+	if len(showStyleList) > 0 {
+		for _, cellKey := range showStyleList {
+			// 查找这个单元格的位置,直接map找了,而不是遍历整个单元格
+			cellPosition, ok := calculateCellMap[cellKey]
+			if !ok {
+				utils.FileLog.Error("找不到单元格位置:", cellKey)
+				continue
+			}
+
+			cell := config[cellPosition.Column][cellPosition.Row]
+			val := cell.ShowValue
+			isPercent := false
+			if strings.Contains(val, "%") {
+				isPercent = true
+				val = strings.Trim(val, "%")
+			}
+			_, e := strconv.ParseFloat(val, 64) // 将字符串转换成float类型
+			if e != nil {                       // 如果没有错误发生则返回true,说明该字符串是一个合法的数字
+				continue
+			}
+			var styleConf request.MixCellShowStyle
+			err = json.Unmarshal([]byte(cell.ShowStyle), &styleConf)
+			if err != nil {
+				err = fmt.Errorf("日期计算配置json解析失败失败: %s, Err:%s", config, err.Error())
+				return
+			}
+			if styleConf.Pn != 0 || styleConf.Nt != "" {
+				val = changePointDecimalPlaces(val, styleConf.Pn, styleConf.Nt, isPercent)
+				cell.ShowFormatValue = val
+			} else {
+				cell.ShowFormatValue = cell.ShowValue
+			}
+			config[cellPosition.Column][cellPosition.Row] = cell
+		}
+	}
+	return
+}
+
+// changePointDecimalPlaces 小数点位数加减和百分比格式
+func changePointDecimalPlaces(str string, changeNum int, numberType string, isPercent bool) (newStr string) {
+	newStr = str
+	var decimalPlaces int
+	dotIndex := strings.Index(newStr, ".") // 查找小数点的位置
+	if dotIndex == -1 {
+		decimalPlaces = 0
+	} else {
+		decimalPlaces = len(newStr) - dotIndex - 1
+	}
+	// 把字符串转成浮点数
+	val, _ := strconv.ParseFloat(str, 64)
+	if isPercent {
+		if numberType == "number" { //百分数转成小数
+			val = val / 100
+			if decimalPlaces > 2 {
+				decimalPlaces += 2
+			} else if decimalPlaces == 1 {
+				decimalPlaces += 1
+			} else if decimalPlaces == 0 {
+				if len(str) == 1 {
+					decimalPlaces = 2
+				} else if len(str) == 2 {
+					if str[1] == '0' {
+						decimalPlaces = 1
+					} else {
+						decimalPlaces = 2
+					}
+				}
+			}
+			isPercent = false
+		}
+	} else {
+		if numberType == "percent" {
+			if decimalPlaces > 2 {
+				decimalPlaces -= 2
+			} else if decimalPlaces == 1 {
+				decimalPlaces = 0
+			}
+			val = val * 100
+		}
+	}
+	if decimalPlaces > 0 {
+		val, _ = decimal.NewFromFloat(val).Round(int32(decimalPlaces)).Float64()
+		newStr = strconv.FormatFloat(val, 'f', decimalPlaces, 64)
+	} else {
+		newStr = fmt.Sprintf("%v", val)
+	}
+	// 计算小数位数
+	decimalPlaces = 0
+	dotIndex = strings.Index(newStr, ".") // 查找小数点的位置
+	if dotIndex == -1 {
+		decimalPlaces = 0
+	} else {
+		decimalPlaces = len(newStr) - dotIndex - 1
+	}
+	decimalPlaces += changeNum
+
+	if decimalPlaces < 0 {
+		decimalPlaces = 0
+	}
+	val, _ = decimal.NewFromFloat(val).Round(int32(decimalPlaces)).Float64()
+	newStr = strconv.FormatFloat(val, 'f', decimalPlaces, 64)
+	if numberType == "percent" || isPercent {
+		newStr += "%"
+	}
+	return
+}
+
+func fixCalculateValueConfig(conf string, edbInfoId int) (newConf string, err error) {
+	newConf = conf
+	if edbInfoId == 0 {
+		return
+	}
+	var tmpConfig request.CalculateConf
+	err = json.Unmarshal([]byte(conf), &tmpConfig)
+	if err != nil {
+		return
+	}
+	if tmpConfig.EdbInfoId == 0 {
+		tmpConfig.EdbInfoId = edbInfoId
+		newConfByte, _ := json.Marshal(tmpConfig)
+		newConf = string(newConfByte)
+		return
+	}
+	return
+}

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

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

+ 3 - 3
services/data/predict_edb_info.go

@@ -611,14 +611,14 @@ func GetPredictCalculateDataListByPredictEdbInfo(edbInfo *data_manage.EdbInfo, s
 }
 
 // ModifyPredictEdbBaseInfoBySourceEdb  根据来源ETA指标修改预测指标的基础信息
-func ModifyPredictEdbBaseInfoBySourceEdb(sourceEDdbInfo *data_manage.EdbInfo) {
+func ModifyPredictEdbBaseInfoBySourceEdb(sourceEDdbInfo *data_manage.EdbInfo, frequency, unit string) {
 	list, err := data_manage.GetGroupPredictEdbBySourceEdbInfoId(sourceEDdbInfo.EdbInfoId)
 	if err != nil {
 		return
 	}
 	for _, v := range list {
-		v.Frequency = sourceEDdbInfo.Frequency
-		v.Unit = sourceEDdbInfo.Unit
+		v.Frequency = frequency
+		v.Unit = unit
 		v.Update([]string{"Frequency", "Unit"})
 		AddOrEditEdbInfoToEs(v.EdbInfoId)
 	}

+ 379 - 0
services/excel/excel_to_lucky_sheet.go

@@ -0,0 +1,379 @@
+package excel
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/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
+}

+ 461 - 156
services/excel/lucky_sheet.go

@@ -3,6 +3,7 @@ package excel
 import (
 	"encoding/json"
 	"errors"
+	"eta/eta_mobile/models/data_manage/excel/request"
 	"eta/eta_mobile/utils"
 	"fmt"
 	"github.com/tealeg/xlsx"
@@ -106,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            interface{} `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表单的配置
@@ -317,8 +323,165 @@ type TableDataMerge struct {
 	MergeColumnNum   int `json:"merge_column_num" description:"合并的列数"`
 }
 
+// ToExcel 通过 TableData生成excel表格
+func (tableData TableData) ToExcel() (downloadFilePath string, err error) {
+	//dir, err := os.Executable()
+	//exPath := filepath.Dir(dir)
+	//downloadFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	downloadFilePath, err = getDownloadPath()
+	if err != nil {
+		return
+	}
+	xlsxFile := xlsx.NewFile()
+	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",
+		Vertical:   "center",
+		WrapText:   true,
+	}
+
+	style.Alignment = alignment
+	style.ApplyAlignment = true
+
+	sheet, err := xlsxFile.AddSheet(sheetName)
+	if err != nil {
+		return
+	}
+	for k, v := range tableData.RowWidthList {
+		err = sheet.SetColWidth(k, k, v/10)
+		if err != nil {
+			return
+		}
+	}
+
+	// 单元格高度配置列表
+	lenRowHeight := len(tableData.RowHeightList)
+
+	for index, v := range tableData.TableDataList {
+		tmpRow := sheet.AddRow()
+
+		//设置单元格高度
+		if index < lenRowHeight && tableData.RowHeightList[index] > 0 {
+			tmpRow.SetHeight(tableData.RowHeightList[index] / 2)
+		}
+
+		for _, cellInfo := range v {
+			tmpStyle := new(xlsx.Style)
+			//xlsx.Style{
+			//	Fill:            xlsx.Fill{},
+			//	ApplyBorder:     false,
+			//	ApplyFill:       false,
+			//	ApplyFont:       false,
+			//	NamedStyleIndex: nil,
+			//}
+
+			//fill := *NewFill("solid", "FF000000", "00FF0000")
+			defaultFill := xlsx.DefaultFill()
+			if cellInfo.Background != `` {
+				defaultFill.PatternType = "solid"
+				backgroundColor := cellInfo.Background
+				// 这么做是为了避免传入的是RGB的格式(rgb(255, 255, 255))
+				backgroundColor = getColor(backgroundColor)
+
+				defaultFill.BgColor = strings.TrimPrefix(backgroundColor, "#")
+				defaultFill.FgColor = strings.TrimPrefix(backgroundColor, "#")
+
+			}
+			tmpStyle.Fill = *defaultFill
+
+			// 获取表格字体配置
+			tmpStyle.Font = getExcelFontConf(cellInfo)
+			//获取表格单元格排列配置
+			tmpStyle.ApplyAlignment = true
+			tmpStyle.Alignment = getExcelAlignmentConf(cellInfo)
+			//边框配置
+			tmpStyle.Border = xlsx.Border{
+				Left:        "thin",
+				LeftColor:   "000000",
+				Right:       "thin",
+				RightColor:  "000000",
+				Top:         "thin",
+				TopColor:    "000000",
+				Bottom:      "thin",
+				BottomColor: "000000",
+			}
+			//tmpStyle.ApplyAlignment = true
+			//tmpStyle.Alignment.WrapText = true
+
+			tmpRow := tmpRow.AddCell()
+
+			tmpRow.SetStyle(tmpStyle)
+			valueStr := cellInfo.Monitor
+			if valueStr == `` {
+				//valueStr = fmt.Sprint(cellInfo.Value)
+				if valueStr == `` && cellInfo.CellType.S != nil {
+					//不是设置在单元格上面,而是设置在文本上
+					for _, cellS := range cellInfo.CellType.S {
+						valueStr += fmt.Sprint(cellS.Value)
+					}
+				}
+			}
+			//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)
+			//}
+		}
+	}
+
+	for _, v := range tableData.MergeList {
+		for k, cell := range sheet.Row(v.StartRowIndex).Cells {
+			if v.StartColumnIndex == k {
+				cell.Merge(v.MergeColumnNum, v.MergeRowNum)
+			}
+		}
+	}
+
+	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)
@@ -399,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)
+	}
 
 	// 表格数据
 	{
@@ -462,21 +630,24 @@ func handleTableDataList(tableDataList [][]LuckySheetDataValue, luckySheetDataCo
 		flag = false
 		//尾部
 		deleteBottomRowIndexList := make([]int, 0)
-		for rowIndex := lenRow - 1; rowIndex >= 0; rowIndex-- {
-			isDelete := true
-			for _, v := range tableDataList[rowIndex] {
-				if v.Monitor != `` {
-					isDelete = false
-					flag = true
+		// 数据要大于1行才会处理
+		if len(tableDataList) > 1 {
+			for rowIndex := lenRow - 1; rowIndex >= 0; rowIndex-- {
+				isDelete := true
+				for _, v := range tableDataList[rowIndex] {
+					if v.Monitor != `` {
+						isDelete = false
+						flag = true
+						break
+					}
+				}
+				if flag {
 					break
 				}
-			}
-			if flag {
-				break
-			}
-			if isDelete {
-				deleteBottomRowIndexList = append(deleteBottomRowIndexList, rowIndex)
-				removeBottomRow++
+				if isDelete {
+					deleteBottomRowIndexList = append(deleteBottomRowIndexList, rowIndex)
+					removeBottomRow++
+				}
 			}
 		}
 
@@ -528,25 +699,28 @@ func handleTableDataList(tableDataList [][]LuckySheetDataValue, luckySheetDataCo
 		flag = false
 		//右边
 		deleteTailColumnIndexList := make([]int, 0)
-		for columnIndex := lenColumn - 1; columnIndex >= 0; columnIndex-- {
-			isDelete := true
-			for _, v := range tableDataList {
-				//如果一列都没有,说明是上面几行是空行,没有数据
-				if len(v) <= 0 {
-					continue
+		// 数据要大于1列才会处理
+		if lenColumn > 1 {
+			for columnIndex := lenColumn - 1; columnIndex >= 0; columnIndex-- {
+				isDelete := true
+				for _, v := range tableDataList {
+					//如果一列都没有,说明是上面几行是空行,没有数据
+					if len(v) <= 0 {
+						continue
+					}
+					if v[columnIndex].Monitor != `` || (v[columnIndex].MergeCell.Column != columnIndex && v[columnIndex].MergeCell.Column != 0) {
+						isDelete = false
+						flag = true
+						break
+					}
 				}
-				if v[columnIndex].Monitor != `` || (v[columnIndex].MergeCell.Column != columnIndex && v[columnIndex].MergeCell.Column != 0) {
-					isDelete = false
-					flag = true
+				if flag {
 					break
 				}
-			}
-			if flag {
-				break
-			}
-			if isDelete {
-				deleteTailColumnIndexList = append(deleteTailColumnIndexList, columnIndex)
-				removeRightColumn++
+				if isDelete {
+					deleteTailColumnIndexList = append(deleteTailColumnIndexList, columnIndex)
+					removeRightColumn++
+				}
 			}
 		}
 
@@ -590,127 +764,14 @@ func handleTableDataList(tableDataList [][]LuckySheetDataValue, luckySheetDataCo
 	}, rowHeightList, rowWidthList, tableDataMergeList
 }
 
+// ToExcel 通过 luckySheetData生成excel表格
 func (item *LuckySheetData) ToExcel() (downloadFilePath string, err error) {
-	tableData, err := item.GetTableDataByLuckySheetDataStr()
-
-	//dir, err := os.Executable()
-	//exPath := filepath.Dir(dir)
-	//downloadFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
-	downloadFilePath, err = getDownloadPath()
+	tableData, err := item.GetTableDataByLuckySheetDataStr(true)
 	if err != nil {
 		return
 	}
-	xlsxFile := xlsx.NewFile()
-	if err != nil {
-		return
-	}
-	style := xlsx.NewStyle()
-	alignment := xlsx.Alignment{
-		Horizontal: "center",
-		Vertical:   "center",
-		WrapText:   true,
-	}
-
-	style.Alignment = alignment
-	style.ApplyAlignment = true
-
-	sheet, err := xlsxFile.AddSheet("sheet1")
-	if err != nil {
-		return
-	}
-	for k, v := range tableData.RowWidthList {
-		err = sheet.SetColWidth(k, k, v/10)
-		if err != nil {
-			return
-		}
-	}
-
-	for index, v := range tableData.TableDataList {
-		tmpRow := sheet.AddRow()
-
-		//设置单元格高度
-		if tableData.RowHeightList[index] > 0 {
-			tmpRow.SetHeight(tableData.RowHeightList[index] / 2)
-		}
-
-		for _, cellInfo := range v {
-			tmpStyle := new(xlsx.Style)
-			//xlsx.Style{
-			//	Fill:            xlsx.Fill{},
-			//	ApplyBorder:     false,
-			//	ApplyFill:       false,
-			//	ApplyFont:       false,
-			//	NamedStyleIndex: nil,
-			//}
-
-			//fill := *NewFill("solid", "FF000000", "00FF0000")
-			defaultFill := xlsx.DefaultFill()
-			if cellInfo.Background != `` {
-				defaultFill.PatternType = "solid"
-				backgroundColor := cellInfo.Background
-				// 这么做是为了避免传入的是RGB的格式(rgb(255, 255, 255))
-				backgroundColor = getColor(backgroundColor)
-
-				defaultFill.BgColor = strings.TrimPrefix(backgroundColor, "#")
-				defaultFill.FgColor = strings.TrimPrefix(backgroundColor, "#")
-
-			}
-			tmpStyle.Fill = *defaultFill
-
-			// 获取表格字体配置
-			tmpStyle.Font = getExcelFontConf(cellInfo)
-			//获取表格单元格排列配置
-			tmpStyle.ApplyAlignment = true
-			tmpStyle.Alignment = getExcelAlignmentConf(cellInfo)
-			//边框配置
-			tmpStyle.Border = xlsx.Border{
-				Left:        "thin",
-				LeftColor:   "000000",
-				Right:       "thin",
-				RightColor:  "000000",
-				Top:         "thin",
-				TopColor:    "000000",
-				Bottom:      "thin",
-				BottomColor: "000000",
-			}
-			//tmpStyle.ApplyAlignment = true
-			//tmpStyle.Alignment.WrapText = true
-
-			tmpRow := tmpRow.AddCell()
-
-			tmpRow.SetStyle(tmpStyle)
-			valueStr := cellInfo.Monitor
-			if valueStr == `` {
-				//valueStr = fmt.Sprint(cellInfo.Value)
-				if valueStr == `` && cellInfo.CellType.S != nil {
-					//不是设置在单元格上面,而是设置在文本上
-					for _, cellS := range cellInfo.CellType.S {
-						valueStr += fmt.Sprint(cellS.Value)
-					}
-				}
-			}
-			tmpRow.SetString(valueStr)
-			//if cellInfo.Function != `` {
-			//	tmpRow.SetFormula(cellInfo.Function)
-			//}
-		}
-	}
+	downloadFilePath, err = tableData.ToExcel()
 
-	for _, v := range tableData.MergeList {
-		for k, cell := range sheet.Row(v.StartRowIndex).Cells {
-			if v.StartColumnIndex == k {
-				cell.Merge(v.MergeColumnNum, v.MergeRowNum)
-			}
-		}
-	}
-	//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
 }
 
@@ -1145,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 {
@@ -1155,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 {
@@ -1394,3 +1458,244 @@ func getColor(bgStr string) string {
 	}
 	return bgStr
 }
+
+// GetTableDataByCustomData 通过自定义表格数据获取表格数据
+func GetTableDataByCustomData(excelType int, data request.TableDataReq) (selfTableData TableData, err error) {
+	tableDataList := make([][]LuckySheetDataValue, 0)
+	mergeList := make([]TableDataMerge, 0)
+
+	// 指标数
+	lenEdb := len(data.Data)
+	if lenEdb <= 0 {
+		return
+	}
+
+	// 日期数
+	lenCol := len(data.Data[0].Data)
+	if lenCol <= 0 {
+		return
+	}
+	// 指标列:1;指标行:2
+	if excelType == 1 {
+		mergeList = append(mergeList, TableDataMerge{
+			StartRowIndex:    0,
+			StartColumnIndex: 0,
+			MergeRowNum:      1,
+			MergeColumnNum:   0,
+		})
+		// 第一行
+		{
+			firstCol := make([]LuckySheetDataValue, 0)
+			firstCol = append(firstCol, LuckySheetDataValue{
+				Value:   "日期",
+				Monitor: "日期",
+				MergeCell: LuckySheetDataConfigMerge{
+					Row:    0, //行数
+					Column: 0, //列数
+					Rs:     2, //合并的行数
+					Cs:     1, //合并的列数
+				},
+			})
+
+			for _, v := range data.Data {
+				edbName := v.EdbName
+				if v.EdbAliasName != `` {
+					edbName = v.EdbAliasName
+				}
+				firstCol = append(firstCol, LuckySheetDataValue{
+					Value:     edbName,
+					Monitor:   edbName,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			tableDataList = append(tableDataList, firstCol)
+		}
+
+		// 第二行
+		{
+			secondCol := make([]LuckySheetDataValue, 0)
+			secondCol = append(secondCol, LuckySheetDataValue{})
+
+			for _, v := range data.Data {
+				name := v.Unit + " / " + v.Frequency
+				secondCol = append(secondCol, LuckySheetDataValue{
+					Value:     name,
+					Monitor:   name,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			tableDataList = append(tableDataList, secondCol)
+		}
+
+		// 开始数据了
+		{
+			for i := 0; i < lenCol; i++ {
+				dataCol := make([]LuckySheetDataValue, 0)
+
+				// 日期
+				dataCol = append(dataCol, LuckySheetDataValue{
+					Value:     data.Data[0].Data[i].DataTime,
+					Monitor:   data.Data[0].Data[i].DataTime,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+
+				// 数据值
+				for _, v := range data.Data {
+					dataCol = append(dataCol, LuckySheetDataValue{
+						Value:     v.Data[i].Value,
+						Monitor:   v.Data[i].ShowValue,
+						MergeCell: LuckySheetDataConfigMerge{},
+					})
+				}
+
+				tableDataList = append(tableDataList, dataCol)
+			}
+		}
+
+		// 开始文本行了
+		{
+			for _, textColList := range data.TextRowData {
+				dataCol := make([]LuckySheetDataValue, 0)
+				for _, v := range textColList {
+					dataCol = append(dataCol, LuckySheetDataValue{
+						Value:     v.Value,
+						Monitor:   v.ShowValue,
+						MergeCell: LuckySheetDataConfigMerge{},
+					})
+				}
+				tableDataList = append(tableDataList, dataCol)
+			}
+		}
+	} else {
+		// 指标行
+
+		mergeList = append(mergeList, TableDataMerge{
+			StartRowIndex:    0,
+			StartColumnIndex: 0,
+			MergeRowNum:      0,
+			MergeColumnNum:   1,
+		})
+
+		// 第一行
+		{
+			firstCol := make([]LuckySheetDataValue, 0)
+			firstCol = append(firstCol, LuckySheetDataValue{
+				Value:   "日期",
+				Monitor: "日期",
+				MergeCell: LuckySheetDataConfigMerge{
+					Row:    0, //行数
+					Column: 0, //列数
+					Rs:     1, //合并的行数
+					Cs:     2, //合并的列数
+				},
+			})
+			firstCol = append(firstCol, LuckySheetDataValue{})
+
+			// 日期列
+			for _, v := range data.Data[0].Data {
+				firstCol = append(firstCol, LuckySheetDataValue{
+					Value:     v.DataTime,
+					Monitor:   v.DataTime,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			// 文本列
+			for _, textColList := range data.TextRowData {
+				firstCol = append(firstCol, LuckySheetDataValue{
+					Value:     textColList[0].Value,
+					Monitor:   textColList[0].ShowValue,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			tableDataList = append(tableDataList, firstCol)
+		}
+
+		// 日期列+文本列+两列表头(日期这个文案表头)
+		//colLen := lenCol+2+len(data.TextRowData)
+		for i := 0; i < lenEdb; i++ {
+			dataCol := make([]LuckySheetDataValue, 0)
+
+			// 指标信息
+			tmpEdbInfo := data.Data[i]
+
+			// 指标名称
+			{
+				edbName := tmpEdbInfo.EdbName
+				if tmpEdbInfo.EdbAliasName != `` {
+					edbName = tmpEdbInfo.EdbAliasName
+				}
+				dataCol = append(dataCol, LuckySheetDataValue{
+					Value:     edbName,
+					Monitor:   edbName,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			// 指标单位、频度
+			{
+				name := tmpEdbInfo.Unit + " / " + tmpEdbInfo.Frequency
+				dataCol = append(dataCol, LuckySheetDataValue{
+					Value:     name,
+					Monitor:   name,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			// 指标数据列
+			for _, tmpData := range tmpEdbInfo.Data {
+				dataCol = append(dataCol, LuckySheetDataValue{
+					Value:     tmpData.Value,
+					Monitor:   tmpData.ShowValue,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			// 文本列
+			for _, textColList := range data.TextRowData {
+				dataCol = append(dataCol, LuckySheetDataValue{
+					Value:     textColList[i+1].Value,
+					Monitor:   textColList[i+1].ShowValue,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+
+			tableDataList = append(tableDataList, dataCol)
+		}
+
+	}
+
+	selfTableData.MergeList = mergeList
+	selfTableData.TableDataList = tableDataList
+
+	return
+}
+
+// GetTableDataByMixedTableData 通过混合表格数据获取表格数据
+func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq) (selfTableData TableData, err error) {
+	tableDataList := make([][]LuckySheetDataValue, 0)
+	mergeList := make([]TableDataMerge, 0)
+
+	// 开始文本行了
+	{
+		for _, row := range config {
+			dataCol := make([]LuckySheetDataValue, 0)
+			for _, cell := range row {
+				dataCol = append(dataCol, LuckySheetDataValue{
+					Value:     cell.Value,
+					Monitor:   cell.ShowValue,
+					MergeCell: LuckySheetDataConfigMerge{},
+				})
+			}
+			tableDataList = append(tableDataList, dataCol)
+		}
+	}
+
+	selfTableData.MergeList = mergeList
+	selfTableData.TableDataList = tableDataList
+
+	return
+}

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

+ 62 - 0
services/excel_info.go

@@ -0,0 +1,62 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/system"
+	"eta/eta_mobile/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*3) //3分钟缓存
+		}
+	} else if status == 0 {
+		//清除编辑缓存
+		_ = utils.Rc.Delete(key)
+	}
+	return
+}

+ 85 - 0
utils/calculate.go

@@ -1,9 +1,11 @@
 package utils
 
 import (
+	"fmt"
 	"github.com/gonum/stat"
 	"github.com/shopspring/decimal"
 	"math"
+	"strings"
 )
 
 // Series is a container for a series of data
@@ -167,3 +169,86 @@ func CalculationDecisive(sList []Coordinate) (r2 float64) {
 func CalculateStandardDeviation(data []float64) float64 {
 	return stat.StdDev(data, nil)
 }
+
+func ReplaceFormula(valArr map[string]float64, formulaStr string) string {
+	funMap := getFormulaMap()
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, k, v, -1)
+	}
+
+	replaceCount := 0
+	for tag, val := range valArr {
+		dvStr := fmt.Sprintf("%v", val)
+		formulaStr = strings.Replace(formulaStr, tag, dvStr, -1)
+		replaceCount++
+	}
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, v, k, -1)
+	}
+	return formulaStr
+}
+
+// CellPosition
+// @Description: 单元格位置
+type CellPosition struct {
+	Tag   string
+	Row   int
+	Value float64
+}
+
+// ReplaceFormulaByCellList
+// @Description: 根据单元格列表替换
+// @author: Roc
+// @datetime2023-11-14 16:16:12
+// @param cellList []CellPosition
+// @param formulaStr string
+// @return string
+func ReplaceFormulaByCellList(cellList []CellPosition, formulaStr string) string {
+	funMap := getFormulaMap()
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, k, v, -1)
+	}
+
+	replaceCount := 0
+	for _, cell := range cellList {
+		dvStr := fmt.Sprintf("%v", cell.Value)
+		formulaStr = strings.Replace(formulaStr, fmt.Sprint(cell.Tag, cell.Row), dvStr, -1)
+		replaceCount++
+	}
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, v, k, -1)
+	}
+	return formulaStr
+}
+
+func ReplaceFormulaByTagMap(valTagMap map[string]int, formulaStr string) string {
+	funMap := getFormulaMap()
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, k, v, -1)
+	}
+
+	replaceCount := 0
+	for tag, val := range valTagMap {
+		dvStr := fmt.Sprintf("%v", val)
+		formulaStr = strings.Replace(formulaStr, tag, dvStr, -1)
+		replaceCount++
+	}
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, v, k, -1)
+	}
+	return formulaStr
+}
+
+func getFormulaMap() map[string]string {
+	funMap := make(map[string]string)
+	funMap["MAX"] = "[@@]"
+	funMap["MIN"] = "[@!]"
+	funMap["ABS"] = "[@#]"
+	funMap["CEIL"] = "[@$]"
+	funMap["COS"] = "[@%]"
+	funMap["FLOOR"] = "[@^]"
+	funMap["MOD"] = "[@&]"
+	funMap["POW"] = "[@*]"
+	funMap["ROUND"] = "[@(]"
+	return funMap
+}

+ 152 - 0
utils/common.go

@@ -603,6 +603,27 @@ func ConvertToFormatDay(excelDaysString string) string {
 	return resultTime
 }
 
+// GetDaysDiff1900 计算日期距离1900-01-01的天数
+func GetDaysDiff1900(date string) int {
+	// 将字符串转换为时间类型
+	//从1899年12月30日开始是因为Excel的日期计算是基于1900年1月1日的,而1900年并不是闰年,因此Excel日期计算存在一个错误。为了修正这个错误,
+	//我们将时间回溯到1899年12月30日,这样在进行日期计算时可以正确地处理Excel日期。
+	tStart, err := time.ParseInLocation(FormatDate, "1899-12-30", time.Local)
+	if err != nil {
+		return 0
+	}
+	tEnd, err := time.ParseInLocation(FormatDate, date, time.Local)
+	if err != nil {
+		return 0
+	}
+
+	// 计算两个日期之间的天数差值
+	duration := tEnd.Sub(tStart).Hours() / 24
+	days := int(duration)
+
+	return days
+}
+
 func CheckPwd(pwd string) bool {
 	compile := `([0-9a-z]+){6,12}|(a-z0-9]+){6,12}`
 	reg := regexp.MustCompile(compile)
@@ -2050,3 +2071,134 @@ func GetColorMap() map[int]string {
 
 	return colorMap
 }
+
+// IsPercentage
+// @Description: 判断一个字符串是否是百分比的字符串,并返回百分比的值(比如:50%,返回true 50)
+// @author: Roc
+// @datetime 2023-11-29 14:00:31
+// @param input string
+// @return bool
+// @return string
+func IsPercentage(input string) (bool, string) {
+	percentagePattern := `^(\d{1,2}(\.\d{1,2})?)%$`
+	match, _ := regexp.MatchString(percentagePattern, input)
+
+	if match {
+		// 提取百分比值
+		re := regexp.MustCompile(percentagePattern)
+		match := re.FindStringSubmatch(input)
+		return true, match[1]
+	}
+
+	return false, ""
+}
+
+// GetLikeKeyword
+//
+//	@Description: 获取sql查询中的like查询字段
+//	@author: Roc
+//	@datetime2023-10-23 14:46:32
+//	@param keyword string
+//	@return string
+func GetLikeKeyword(keyword string) string {
+	return `%` + keyword + `%`
+}
+
+// GetLikeKeywordPars
+//
+//	@Description: 获取sql查询中的参数切片
+//	@author: Roc
+//	@datetime2023-10-23 14:50:18
+//	@param pars []interface{}
+//	@param keyword string
+//	@param num int
+//	@return newPars []interface{}
+func GetLikeKeywordPars(pars []interface{}, keyword string, num int) (newPars []interface{}) {
+	newPars = pars
+	if newPars == nil {
+		newPars = make([]interface{}, 0)
+	}
+	for i := 1; i <= num; i++ {
+		newPars = append(newPars, `%`+keyword+`%`)
+	}
+	return
+}
+
+// FormatMixTableDataShowValue 格式化自定表格显示数据
+func FormatMixTableDataShowValue(x float64) (res string) {
+	res = fmt.Sprint(x)
+	return
+}
+
+// FormatTableDataShowValue 格式化自定表格显示数据
+func FormatTableDataShowValue(x float64) (res string) {
+	if x > 1 || x < -1 {
+		res = decimal.NewFromFloat(x).Round(2).String()
+		return
+	}
+	// 介于-1到1之间
+	xStr := strconv.FormatFloat(x, 'f', -10, 64)
+
+	// 使用 strings.Split 函数将小数拆分为整数部分和小数部分
+	xParts := strings.Split(xStr, ".")
+	if len(xParts) < 2 {
+		res = fmt.Sprint(x)
+		return
+	}
+
+	// 计算小数部分的长度,即小数点后面的位数
+	xDecimals := len(xParts[1])
+	if xDecimals > 2 {
+		parts := xParts[1]
+		// 小数点后小于等于两位, 直接拼接返回
+		partLen := len(xParts[1])
+		if partLen <= 2 {
+			res = xParts[0] + "." + parts
+			return
+		}
+		// 找出第一个有效数字, 算出n
+		one := 0
+		for k, p := range parts {
+			// 48->0
+			if p != 48 {
+				if one == 0 {
+					one = k + 1
+				}
+			}
+		}
+		n := partLen - one
+		if n >= 1 {
+			n -= 1
+		} else {
+			one -= 1
+		}
+
+		var partFloat float64
+		partInt, _ := strconv.Atoi(parts)
+		partFloat, _ = decimal.NewFromInt(int64(partInt)).Div(decimal.NewFromFloat(math.Pow(10, float64(n)))).Float64()
+		partFloat = math.Round(partFloat)
+		partFloat, _ = decimal.NewFromFloat(partFloat).Div(decimal.NewFromFloat(math.Pow(10, float64(one+1)))).Float64()
+		resParts := strings.Split(fmt.Sprint(partFloat), ".")
+		resPart := ""
+		if len(resParts) > 1 {
+			resPart = resParts[1]
+		} else {
+			resPart = resParts[0]
+		}
+		res = xParts[0] + "." + resPart
+	}
+
+	if xDecimals <= 2 {
+		xDecimalsStr := xParts[1]
+		x, _ = strconv.ParseFloat(xParts[0]+"."+xDecimalsStr, 64)
+		res = xParts[0] + "." + xDecimalsStr
+	}
+	return
+}
+
+func DealDateTimeZero(t time.Time, format string) (timeStr string) {
+	if !t.IsZero() {
+		timeStr = t.Format(format)
+	}
+	return
+}

+ 8 - 0
utils/constants.go

@@ -331,6 +331,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  //曲线图

+ 15 - 0
utils/sort.go

@@ -0,0 +1,15 @@
+package utils
+
+type StrArr []string
+
+func (a StrArr) Len() int {
+	return len(a)
+}
+
+func (a StrArr) Less(i, j int) bool {
+	return a[i] > a[j] // 降序
+}
+
+func (a StrArr) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio