فهرست منبع

Merge branch 'dm' of http://8.136.199.33:3000/eta_server/eta_api into dm

kobe6258 2 ماه پیش
والد
کامیت
a4cd170705

+ 25 - 24
controllers/data_manage/bloomberg_data.go

@@ -7,7 +7,6 @@ import (
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services/data"
-	"eta/eta_api/services/elastic"
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
@@ -75,8 +74,8 @@ func (this *BloombergDataController) List() {
 		params.Keywords = strings.TrimSpace(params.Keywords)
 		if params.Keywords != "" {
 			// 空格分词搜
-			//indexCodeCol := data_manage.BaseFromBloombergIndexCols.IndexCode
-			//indexNameCol := data_manage.BaseFromBloombergIndexCols.IndexName
+			indexCodeCol := data_manage.BaseFromBloombergIndexCols.IndexCode
+			indexNameCol := data_manage.BaseFromBloombergIndexCols.IndexName
 			//keywordArr := strings.Split(params.Keywords, " ")
 			//if len(keywordArr) > 1 {
 			//	sliceArr := make([]string, 0)
@@ -95,29 +94,31 @@ func (this *BloombergDataController) List() {
 			//	cond += fmt.Sprintf(` AND (%s LIKE ? OR %s LIKE ?)`, indexCodeCol, indexNameCol)
 			//	pars = utils.GetLikeKeywordPars(pars, params.Keywords, 2)
 			//}
+			cond += fmt.Sprintf(` AND (%s LIKE ? OR %s LIKE ?)`, indexCodeCol, indexNameCol)
+			pars = utils.GetLikeKeywordPars(pars, params.Keywords, 2)
 
 			// ES搜
-			_, list, e := elastic.SearchDataSourceIndex(utils.EsDataSourceIndexName, params.Keywords, utils.DATA_SOURCE_BLOOMBERG, 0, []int{}, []int{}, []string{}, startSize, params.PageSize)
-			if e != nil {
-				br.Msg = "获取失败"
-				br.ErrMsg = fmt.Sprintf("ES-搜索Bloomberg指标失败, %v", e)
-				return
-			}
-			if len(list) == 0 {
-				dataResp.Paging = paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
-				dataResp.List = make([]*data_manage.BaseFromBloombergIndexItem, 0)
-				br.Data = dataResp
-				br.Ret = 200
-				br.Success = true
-				br.Msg = "获取成功"
-				return
-			}
-			var indexIds []int
-			for _, v := range list {
-				indexIds = append(indexIds, v.PrimaryId)
-			}
-			cond += fmt.Sprintf(" AND %s IN (%s)", data_manage.BaseFromBloombergIndexCols.BaseFromBloombergIndexId, utils.GetOrmInReplace(len(indexIds)))
-			pars = append(pars, indexIds)
+			//_, list, e := elastic.SearchDataSourceIndex(utils.EsDataSourceIndexName, params.Keywords, utils.DATA_SOURCE_BLOOMBERG, 0, []int{}, []int{}, []string{}, startSize, params.PageSize)
+			//if e != nil {
+			//	br.Msg = "获取失败"
+			//	br.ErrMsg = fmt.Sprintf("ES-搜索Bloomberg指标失败, %v", e)
+			//	return
+			//}
+			//if len(list) == 0 {
+			//	dataResp.Paging = paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+			//	dataResp.List = make([]*data_manage.BaseFromBloombergIndexItem, 0)
+			//	br.Data = dataResp
+			//	br.Ret = 200
+			//	br.Success = true
+			//	br.Msg = "获取成功"
+			//	return
+			//}
+			//var indexIds []int
+			//for _, v := range list {
+			//	indexIds = append(indexIds, v.PrimaryId)
+			//}
+			//cond += fmt.Sprintf(" AND %s IN (%s)", data_manage.BaseFromBloombergIndexCols.BaseFromBloombergIndexId, utils.GetOrmInReplace(len(indexIds)))
+			//pars = append(pars, indexIds)
 		}
 
 		if params.Frequency != "" {

+ 79 - 0
controllers/data_manage/chart_info.go

@@ -2,6 +2,7 @@ package data_manage
 
 import (
 	"encoding/json"
+	"errors"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
@@ -10,6 +11,7 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/area_graph"
 	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/services/data/excel"
 	"eta/eta_api/services/eta_forum"
@@ -1374,6 +1376,8 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 		extraConfigStr = chartInfo.BarConfig
 	} else if chartInfo != nil && chartInfo.ChartType == utils.CHART_TYPE_SECTION_COMBINE {
 		extraConfigStr = req.ExtraConfig
+	} else if chartInfo != nil && chartInfo.ChartType == utils.CHART_TYPE_AREA {
+		extraConfigStr = req.ExtraConfig
 	}
 
 	// 获取图表中的指标数据
@@ -1401,6 +1405,16 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
 	}
 
+	// 面积图 面积堆积 数据处理
+	if req.ChartType == utils.CHART_TYPE_AREA {
+		err, errMsg = fillAreaGraphData(extraConfigStr, edbList)
+		if err != nil {
+			br.Msg = "面积图处理失败"
+			br.ErrMsg = errMsg
+			return
+		}
+	}
+
 	//图表操作权限
 	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
 	//判断是否需要展示英文标识
@@ -1493,6 +1507,60 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 	br.Data = resp
 }
 
+func fillAreaGraphData(extraConfigStr string, edbDataList []*data_manage.ChartEdbInfoMapping) (err error, errMsg string) {
+
+	var tmpConfig data_manage.AreaExtraConf
+	if extraConfigStr != `` {
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpConfig)
+		if err != nil {
+			errMsg = "面积图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		if tmpConfig.StandardEdbInfoId <= 0 {
+			utils.FileLog.Info("面积图未开启面积堆积")
+			return
+		}
+	}
+	if tmpConfig.IsHeap == 1 {
+		standardIndexMap := make(map[string]*data_manage.EdbDataList)
+		var startDate, endDate string
+		for _, v := range edbDataList {
+			// 判断是否为基准指标
+			if v.EdbInfoId == tmpConfig.StandardEdbInfoId {
+				if dataList, ok := v.DataList.([]*data_manage.EdbDataList); ok {
+					startDate = dataList[0].DataTime
+					endDate = dataList[len(dataList)-1].DataTime
+					for _, dataObject := range dataList {
+						standardIndexMap[dataObject.DataTime] = dataObject
+					}
+				}
+				break
+			}
+		}
+		strategy, err := area_graph.CreateStrategy(tmpConfig.NullDealWay)
+		if err != nil {
+			return err, "创建空值处理器失败"
+		}
+		err = strategy.Deal(tmpConfig, edbDataList, standardIndexMap, startDate, endDate)
+		if err != nil {
+			return err, err.Error()
+		}
+
+		// 时间戳处理
+		for _, mapping := range edbDataList {
+			if dataList, ok := mapping.DataList.([]*data_manage.EdbDataList); ok {
+				for _, dataInfo := range dataList {
+					toFormatTime := utils.StringToFormatTime(dataInfo.DataTime, utils.FormatDate)
+					dataInfo.DataTimestamp = toFormatTime.UnixMilli()
+				}
+			}
+		}
+	}
+
+	return nil, ""
+}
+
 // ChartInfoDetailV2
 // @Title 获取图表详情
 // @Description 获取图表详情接口
@@ -2825,6 +2893,17 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		chartInfo.ChartClassify = append(chartInfo.ChartClassify, chartClassifyParent)
 	}
 	chartInfo.ChartClassify = append(chartInfo.ChartClassify, chartViewClassify)
+
+	// 面积图 面积堆积 数据处理
+	if chartType == utils.CHART_TYPE_AREA {
+		err, errMsg = fillAreaGraphData(extraConfigStr, edbList)
+		if err != nil {
+			msg = "获取失败"
+			errMsg = "获取面积图数据失败,Err:" + err.Error()
+			return
+		}
+	}
+
 	resp.EdbInfoList = edbList
 	//判断是否需要展示英文标识
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, chartInfo.Source, chartInfo.ChartType)

+ 3 - 0
controllers/data_manage/chart_theme.go

@@ -207,6 +207,9 @@ func (c *ChartThemeController) GetThemePreviewData() {
 		chartInfo.LeftMax = "4000"
 		extraConfigStr = `{"DateConfList":[],"IsHeap":0,"XDataList":[{"Name":"内销"},{"Name":"出口"},{"Name":"销量"},{"Name":"产量"}],"UnitList":{"LeftName":"万台","RightName":"%","RightTwoName":""},"BaseChartSeriesName":"增量","SortType":0,"SeriesList":[{"ChartSeriesId":0,"SeriesName":"增量","ChartStyle":"column","ChartColor":"rgba(0, 0, 255, 1)","ChartWidth":1,"IsPoint":0,"IsNumber":0,"IsAxis":1,"EdbInfoList":[{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":19,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}},{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":20,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}},{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":21,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}},{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":22,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}}]},{"ChartSeriesId":0,"SeriesName":"增速","ChartStyle":"spline","ChartColor":"rgba(255, 0, 200, 1)","ChartWidth":1,"IsPoint":0,"IsNumber":0,"IsAxis":0,"EdbInfoList":[{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":23,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}},{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":24,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}},{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":25,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}},{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":26,"DateConfName":"","DateConfType":0,"DateConf":{"MoveForward":0,"DateChange":[]}}]}]}`
 		chartInfo.ChartName = "图表标题"
+	case utils.CHART_TYPE_AREA: // 曲线图
+		edbInfoIdList = []int{25, 26}
+		chartInfo.ChartName = "面积图"
 	default:
 		br.Msg = "暂不支持该类型"
 		br.IsSendEmail = false

+ 18 - 20
controllers/data_manage/correlation/correlation_chart_classify.go

@@ -449,6 +449,7 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 		}
 	}
 	resp := new(data_manage.AddChartInfoResp)
+
 	//删除图表
 	if req.ChartInfoId > 0 {
 		chartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
@@ -492,13 +493,23 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 		}
 
 		source := chartInfo.Source // 相关性图表(滚动相关性)
-		//删除图表及关联指标
+
+		// 删除图表及关联指标
 		err = data_manage.DeleteChartInfoAndData(chartInfo.ChartInfoId)
 		if err != nil {
 			br.Msg = "删除失败"
 			br.ErrMsg = "删除失败,Err:" + err.Error()
 			return
 		}
+
+		// 删除图表关联
+		e = correlationServ.RemoveCorrelationRelate(chartInfo.ChartInfoId)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = fmt.Sprintf("删除相关性图表关联失败, %v", e)
+			return
+		}
+
 		//删除ES
 		{
 			go data.EsDeleteChartInfo(chartInfo.ChartInfoId)
@@ -507,13 +518,11 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 			go data.EsDeleteMyChartInfoByMyChartIds(myIds)
 		}
 
+		// 删除后定位至其他图(不知道原需求具体定位到哪张...修复的时候只是多加上了个source条件)
 		var condition string
 		var pars []interface{}
-		condition += " AND chart_classify_id=? AND source = ? "
-		pars = append(pars, chartInfo.ChartClassifyId, source)
-
-		condition += " AND chart_info_id>? ORDER BY create_time ASC LIMIT 1 "
-		pars = append(pars, req.ChartInfoId)
+		condition += ` AND chart_classify_id = ? AND source = ? AND chart_info_id > ? ORDER BY create_time ASC LIMIT 1`
+		pars = append(pars, chartInfo.ChartClassifyId, source, req.ChartInfoId)
 
 		nextItem, err := data_manage.GetChartInfoByCondition(condition, pars)
 		if err != nil && !utils.IsErrNoRow(err) {
@@ -529,11 +538,8 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 			var condition string
 			var pars []interface{}
 
-			condition += " AND level=1 "
-			//pars = append(pars, chartInfo.ChartClassifyId)
-
-			condition += " AND chart_classify_id>? ORDER BY chart_classify_id ASC LIMIT 1 "
-			pars = append(pars, chartInfo.ChartClassifyId)
+			condition += ` AND level = 1 AND chart_classify_id > ? AND source = ? ORDER BY chart_classify_id ASC LIMIT 1`
+			pars = append(pars, chartInfo.ChartClassifyId, source)
 
 			classifyItem, err := data_manage.GetChartClassifyByCondition(condition, pars)
 			if err != nil && !utils.IsErrNoRow(err) {
@@ -542,7 +548,7 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 				return
 			}
 			if classifyItem != nil {
-				nextItem, err = data_manage.GetNextChartInfo(chartInfo.ChartClassifyId)
+				nextItem, err = data_manage.GetNextChartByClassifyIdAndSource(chartInfo.ChartClassifyId, source)
 				if err != nil && !utils.IsErrNoRow(err) {
 					br.Msg = "删除失败"
 					br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
@@ -555,14 +561,6 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 			}
 		}
 
-		// 删除图表关联
-		e = correlationServ.RemoveCorrelationRelate(chartInfo.ChartInfoId)
-		if e != nil {
-			br.Msg = "删除失败"
-			br.ErrMsg = fmt.Sprintf("删除相关性图表关联失败, %v", e)
-			return
-		}
-
 		//新增操作日志
 		{
 			chartLog := new(data_manage.ChartInfoLog)

+ 26 - 0
controllers/data_manage/excel/excel_info.go

@@ -123,6 +123,18 @@ func (c *ExcelInfoController) Add() {
 		req.ExcelClassifyId = parentExcelInfo.ExcelClassifyId
 	}
 
+	// 额外配置(表格冻结行列等)
+	var extraConfig string
+	if req.ExtraConfig != nil {
+		b, e := json.Marshal(req.ExtraConfig)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("操作失败, %v", e)
+			return
+		}
+		extraConfig = string(b)
+	}
+
 	var condition string
 	var pars []interface{}
 	condition += " AND excel_classify_id=? AND parent_id=?"
@@ -259,6 +271,7 @@ func (c *ExcelInfoController) Add() {
 		UpdateUserId:       sysUser.AdminId,
 		UpdateUserRealName: sysUser.RealName,
 		SourcesFrom:        req.SourcesFrom,
+		ExtraConfig:        extraConfig,
 	}
 
 	excelEdbMappingList := make([]*excel3.ExcelEdbMapping, 0)
@@ -295,6 +308,7 @@ func (c *ExcelInfoController) Add() {
 			//ParentId:           req.ParentId,
 			UpdateUserId:       sysUser.AdminId,
 			UpdateUserRealName: sysUser.RealName,
+			ExtraConfig:        extraConfig,
 		}
 	}
 	err = excel3.AddExcelInfo(excelInfo, excelEdbMappingList, childExcel)
@@ -1057,6 +1071,18 @@ func (c *ExcelInfoController) Edit() {
 		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "Content", "SourcesFrom"}
 	}
 
+	// 额外配置(表格冻结行列等)
+	if req.ExtraConfig != nil {
+		b, e := json.Marshal(req.ExtraConfig)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("操作失败, %v", e)
+			return
+		}
+		excelInfo.ExtraConfig = string(b)
+		updateExcelInfoParams = append(updateExcelInfoParams, "ExtraConfig")
+	}
+
 	excelEdbMappingList := make([]*excel3.ExcelEdbMapping, 0)
 	if len(edbInfoIdList) > 0 {
 		for _, edbInfoId := range edbInfoIdList {

+ 20 - 21
controllers/data_stat/edb_source_stat.go

@@ -805,24 +805,27 @@ func (this *EdbSourceStatController) EdbUpdateFailedList() {
 	terminalCode := this.GetString("TerminalCode", "")
 	createTime := this.GetString("CreateTime", "")
 
-	if terminalCode == "" {
-		br.Msg = "请选择对应的终端信息"
-		return
-	}
-	terminalInfo, err := data_manage.GetEdbTerminalByTerminalCode(terminalCode)
-	if err != nil {
-		if utils.IsErrNoRow(err) {
-			br.Msg = "终端不存在"
-			return
-		}
-		br.Msg = "查询终端信息出错"
-		br.ErrMsg = "查询终端信息出错 Err:" + err.Error()
-		return
-	}
 	condition := " and source = ? and terminal_code = ?"
 	var pars []interface{}
 	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, terminalCode)
 
+	terminalName := ""
+	terminalDir := ""
+	if terminalCode != "" {
+		terminalInfo, err := data_manage.GetEdbTerminalByTerminalCode(terminalCode)
+		if err != nil {
+			if utils.IsErrNoRow(err) {
+				br.Msg = "终端不存在"
+				return
+			}
+			br.Msg = "查询终端信息出错"
+			br.ErrMsg = "查询终端信息出错 Err:" + err.Error()
+			return
+		}
+		terminalName = terminalInfo.Name
+		terminalDir = terminalInfo.DirPath
+	}
+
 	if createTime != "" {
 		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)
 		if err != nil {
@@ -856,9 +859,9 @@ func (this *EdbSourceStatController) EdbUpdateFailedList() {
 	}
 	resp := data_stat.GetEdbUpdateFailedResp{
 		List:             list,
-		Name:             terminalInfo.Name,
+		Name:             terminalName,
 		TerminalCode:     terminalCode,
-		DirPath:          terminalInfo.DirPath,
+		DirPath:          terminalDir,
 		UpdateSuccessNum: successNum,
 		UpdateFailedNum:  failedNum,
 	}
@@ -909,16 +912,12 @@ func (this *EdbSourceStatController) EdbUpdateFailedDetailList() {
 		return
 	}
 
-	if terminalCode == "" {
-		br.Msg = "请选择对应的终端信息"
-		return
-	}
 	if frequency == "" {
 		br.Msg = "请选择对应的频度"
 		return
 	}
 
-	condition := " and source = ? and terminal_code = ? and frequency=? and data_update_failed_reason=? and data_update_result = 2"
+	condition := " and source = ? AND terminal_code = ? and frequency=? and data_update_failed_reason=? and data_update_result = 2"
 	var pars []interface{}
 	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, terminalCode, frequency, sourceUpdateFailedReason)
 

+ 1 - 0
models/data_manage/chart_edb_mapping.go

@@ -29,6 +29,7 @@ type ChartEdbMapping struct {
 	ChartColor        string    `description:"颜色"`
 	PredictChartColor string    `description:"预测数据的颜色"`
 	ChartWidth        float64   `description:"线条大小"`
+	ChartScale        float64   `description:"参考刻度线"`
 	Source            int       `description:"1:ETA图库;2:商品价格曲线"`
 	EdbAliasName      string    `description:"中文别名"`
 	IsConvert         int       `description:"是否数据转换 0不转 1转"`

+ 20 - 0
models/data_manage/chart_info.go

@@ -64,6 +64,14 @@ type ChartInfo struct {
 	DateTypeNum       int    `description:"date_type=25(N月前)时的N值,其他N值可复用此字段"`
 }
 
+// AreaExtraConf 面积图配置
+type AreaExtraConf struct {
+	IsHeap            int `description:"是否堆积 1-堆积 2-不堆积"`
+	HeapWay           int `description:"堆积方式 1-普通 2-百分比"`
+	StandardEdbInfoId int `description:"基准指标id"`
+	NullDealWay       int `description:"空值处理方式,1-插值填充 2-前值填充 3-后值填充 4-等于0 5-删除日期"`
+}
+
 type ChartInfoMore struct {
 	ChartInfo
 	IsEnChart     bool   `description:"是否展示英文标识"`
@@ -198,6 +206,7 @@ type ChartSaveItem struct {
 	ChartColor        string  `description:"颜色"`
 	PredictChartColor string  `description:"预测数据的颜色"`
 	ChartWidth        float64 `description:"线条大小"`
+	ChartScale        float64 `description:"参考刻度线"`
 	Source            int     `description:"1:ETA图库;2:商品价格曲线"`
 	EdbAliasName      string  `description:"中文别名"`
 	IsConvert         int     `description:"是否数据转换 0不转 1转"`
@@ -728,6 +737,7 @@ type ChartEdbInfoMapping struct {
 	ChartColor          string      `description:"颜色"`
 	PredictChartColor   string      `description:"预测数据的颜色"`
 	ChartWidth          float64     `description:"线条大小"`
+	ChartScale          float64     `description:"参考刻度线"`
 	ChartType           int         `description:"生成样式:1:曲线图,2:季节性图,3:面积图,4:柱状图,5:散点图,6:组合图,7:柱方图,8:商品价格曲线图,9:相关性图"`
 	LatestDate          string      `description:"数据最新日期"`
 	LatestValue         float64     `description:"数据最新值"`
@@ -2953,3 +2963,13 @@ func UpdateChartClassifyIdByChartInfoId(chartInfoIds []int, classifyId int) (err
 	err = o.Exec(sql, classifyId, chartInfoIds).Error
 	return
 }
+
+func GetNextChartByClassifyIdAndSource(classifyId, source int) (item *ChartInfo, err error) {
+	sql := `SELECT b.* FROM chart_classify AS a
+		INNER JOIN chart_info AS b ON a.chart_classify_id=b.chart_classify_id
+		WHERE a.chart_classify_id > ? AND a.source = ?
+		ORDER BY a.chart_classify_id ASC
+		LIMIT 1`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId, source).First(&item).Error
+	return
+}

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

@@ -799,3 +799,18 @@ func (m *ExcelInfo) GetCountByCondition(condition string, pars []interface{}) (c
 	err = o.Raw(sql, pars...).Scan(&count).Error
 	return
 }
+
+// ExcelCommonExtraConfig 表格额外配置
+type ExcelCommonExtraConfig struct {
+	TableFreeze ExcelInfoFreeze `description:"表格冻结"`
+}
+
+// ExcelInfoFreeze 表格冻结
+type ExcelInfoFreeze struct {
+	FreezeFirstRow bool `description:"冻结首行"`
+	FreezeFirstCol bool `description:"冻结首列"`
+	FreezeStartRow int  `description:"冻结开始行"`
+	FreezeEndRow   int  `description:"冻结结束行"`
+	FreezeStartCol int  `description:"冻结开始列"`
+	FreezeEndCol   int  `description:"冻结结束列"`
+}

+ 25 - 20
models/data_manage/excel/request/excel_info.go

@@ -1,6 +1,9 @@
 package request
 
-import "encoding/json"
+import (
+	"encoding/json"
+	"eta/eta_api/models/data_manage/excel"
+)
 
 // MoveExcelInfoReq 移动excel表格请求
 type MoveExcelInfoReq struct {
@@ -17,29 +20,31 @@ type DeleteExcelInfoReq struct {
 
 // AddExcelInfoReq 新增表格请求
 type AddExcelInfoReq struct {
-	ExcelInfoId     int         `description:"表格ID"`
-	ExcelName       string      `description:"表格名称"`
-	Source          int         `description:"表格来源,1:excel插件的表格,2:自定义表格,5平衡表,默认:1"`
-	ExcelType       int         `description:"表格类型,1:指标列,2:日期列,默认:1"`
-	ExcelImage      string      `description:"表格截图"`
-	ExcelClassifyId int         `description:"分类id"`
-	Content         string      `description:"Excel表格内容"`
-	TableData       interface{} `description:"自定义表格的数据内容"`
-	ParentId        int         `description:"表格的父级id"`
-	SourcesFrom     string      `description:"图表来源"`
+	ExcelInfoId     int                           `description:"表格ID"`
+	ExcelName       string                        `description:"表格名称"`
+	Source          int                           `description:"表格来源,1:excel插件的表格,2:自定义表格,5平衡表,默认:1"`
+	ExcelType       int                           `description:"表格类型,1:指标列,2:日期列,默认:1"`
+	ExcelImage      string                        `description:"表格截图"`
+	ExcelClassifyId int                           `description:"分类id"`
+	Content         string                        `description:"Excel表格内容"`
+	TableData       interface{}                   `description:"自定义表格的数据内容"`
+	ParentId        int                           `description:"表格的父级id"`
+	SourcesFrom     string                        `description:"图表来源"`
+	ExtraConfig     *excel.ExcelCommonExtraConfig `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:"自定义表格的数据内容"`
-	SourcesFrom     string      `description:"图表来源"`
-	IsColChange     bool        `description:"是否修改过行列,true时清空引用"`
+	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:"自定义表格的数据内容"`
+	SourcesFrom     string                        `description:"图表来源"`
+	IsColChange     bool                          `description:"是否修改过行列,true时清空引用"`
+	ExtraConfig     *excel.ExcelCommonExtraConfig `description:"表格额外配置"`
 }
 
 // SetExcelInfoImageReq 设置excel表格图片请求

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

@@ -95,6 +95,7 @@ type ExcelInfoDetail struct {
 	SourcesFrom        string                       `description:"图表来源"`
 	ExcelSource        string                       `description:"表格来源str"`
 	ExcelSourceEn      string                       `description:"表格来源(英文)"`
+	ExtraConfig        excel2.ExcelCommonExtraConfig `description:"表格额外配置"`
 }
 
 type BalanceChildTableResp struct {

+ 1 - 0
models/data_manage/stl/response/stl.go

@@ -5,6 +5,7 @@ type StlPreviewResp struct {
 	TrendChartInfo    ChartEdbInfo
 	SeasonalChartInfo ChartEdbInfo
 	ResidualChartInfo ChartEdbInfo
+	NonTrendChartInfo ChartEdbInfo
 	EvaluationResult  EvaluationResult
 }
 

+ 87 - 36
services/binlog/binlog.go

@@ -17,29 +17,31 @@ func ListenMysql() {
 	var err error
 	defer func() {
 		if err != nil {
-			fmt.Println("数据库监听服务异常,err:", err)
+			tips := fmt.Sprintf("ListenMysql-数据库监听服务异常, %v", err)
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
 		}
+		utils.FileLog.Info("ListenMysql end")
 	}()
 	if utils.MYSQL_DATA_BINLOG_URL == "" {
-		panic("mysql url is empty")
+		err = fmt.Errorf("mysql url is empty")
+		return
 	}
-
 	if utils.MYSQL_DATA_BINLOG_USER == "" {
-		panic("mysql user is empty")
+		err = fmt.Errorf("mysql user is empty")
+		return
 	}
 	if utils.MYSQL_DATA_BINLOG_PWD == "" {
-		panic("mysql password is empty")
+		err = fmt.Errorf("mysql password is empty")
+		return
 	}
 	if utils.MYSQL_DATA_BINLOG_DB == "" {
-		panic("mysql db is empty")
+		err = fmt.Errorf("mysql db is empty")
+		return
 	}
 
 	includeTableRegex := []string{
 		utils.MYSQL_DATA_BINLOG_DB + ".edb_info$",
-		// utils.MYSQL_DATA_BINLOG_DB + ".edb_classify$",
-		// utils.MYSQL_DATA_BINLOG_DB + ".base_from_mysteel_chemical_index$",
-		// utils.MYSQL_DATA_BINLOG_DB + ".base_from_smm_index$",
-		// utils.MYSQL_DATA_BINLOG_DB + ".edb_data*",
 
 		// 数据源
 		utils.MYSQL_DATA_BINLOG_DB + ".base_from_rzd_index$",
@@ -104,47 +106,77 @@ func ListenMysql() {
 	}
 
 	// 校验mysql binlog format,目前仅支持row格式
-	{
-		binlogFormat, tmpErr := binlog.GetBinlogFormat()
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-
-		if binlogFormat.Value != "ROW" {
-			panic("mysql binlog format is not ROW")
-		}
+	binlogFormat, e := binlog.GetBinlogFormat()
+	if e != nil {
+		err = fmt.Errorf("get binlog format err: %v", e)
+		return
+	}
+	if binlogFormat.Value != "ROW" {
+		err = fmt.Errorf("mysql binlog format is not ROW")
+		return
 	}
 
-	//  获取上一次启动时的binlog文件名称和位置
-	fileName, position, err := getBinlogNamePosition()
-	if err != nil {
+	// 获取上一次启动时的binlog文件名称和位置
+	fileName, position, e := getBinlogNamePosition()
+	if e != nil {
+		err = fmt.Errorf("获取binlog文件名称和位置失败, %v", e)
 		return
 	}
+
 	// 修改记录本次启动时的binlog文件名称和位置
 	modifyBinlogNamePosition(fileName, position)
-	// 定时修改binlog文件名称和位置
-	go timingModifyBinlogNamePosition()
 
-	c, err := canal.NewCanal(cfg)
-	if err != nil {
-		fmt.Println("err:", err)
+	c, e := canal.NewCanal(cfg)
+	if e != nil {
+		err = fmt.Errorf("new canal err: %v", e)
 		return
 	}
-	utils.FileLog.Debug("记录上一次启动时的fileName:", fileName, ";position:", position)
+	utils.FileLog.Debug(fmt.Sprintf("记录上一次启动时的fileName: %s, position: %d", fileName, position))
 
 	binlogHandler := &EdbEventHandler{}
 	binlogHandler.SetBinlogFileName(fileName, position)
 	c.SetEventHandler(binlogHandler)
 	//c.Run()
-	// 同步到redis
-	go binlogHandler.SyncToRedis()
 
 	pos := mysql.Position{
 		Name: fileName,
 		Pos:  position,
 	}
-	err = c.RunFrom(pos)
+	if e = c.RunFrom(pos); e != nil {
+		fmt.Printf("启动监听异常: %v, 重新定位binlog\n", e)
+		if c != nil {
+			c.Close()
+		}
+
+		// 重新获取binlog位置
+		rename, reposition, e := initBinlogNamePosition()
+		if e != nil {
+			err = fmt.Errorf("重新定位binlog失败, %v", e)
+			return
+		}
+		pos.Name = rename
+		pos.Pos = reposition
+
+		// 重新尝试监听, 再起不来就退出报异常
+		canalNew, e := canal.NewCanal(cfg)
+		if e != nil {
+			err = fmt.Errorf("renew canal err: %v", e)
+			return
+		}
+		binlogHandler.SetBinlogFileName(rename, reposition)
+		canalNew.SetEventHandler(binlogHandler)
+		c = canalNew
+		if e = c.RunFrom(pos); e != nil {
+			err = fmt.Errorf("重新监听binlog失败, %v", e)
+			return
+		}
+	}
+
+	// 定时修改binlog文件名称和位置
+	go timingModifyBinlogNamePosition()
+
+	// 同步到redis
+	go binlogHandler.SyncToRedis()
 }
 
 // getBinlogNamePosition
@@ -170,19 +202,18 @@ func getBinlogNamePosition() (fileName string, position uint32, err error) {
 
 	// 如果没有从redis中获取到上次监听到的binlog的文件名称,或者位置为0,则从mysql中获取,则从 MySQL 中获取最新的文件名和位置。
 	if fileName == `` || position == 0 {
-
 		// binlog文件名
 		fileNameKey := binlog.BinlogFileNameKey
 		fileNameLog, tmpErr := binlog.GetBusinessSysInteractionLogByKey(fileNameKey)
 		if tmpErr == nil {
-			fileName = fileNameLog.InteractionKey
+			fileName = fileNameLog.InteractionVal
 		}
 
 		// binlog位置
 		positionKey := binlog.BinlogPositionKey
 		positionLog, tmpErr := binlog.GetBusinessSysInteractionLogByKey(positionKey)
 		if tmpErr == nil {
-			positionStr := positionLog.InteractionKey
+			positionStr := positionLog.InteractionVal
 			positionInt, tmpErr := strconv.Atoi(positionStr)
 			if tmpErr == nil {
 				position = uint32(positionInt)
@@ -214,7 +245,7 @@ func modifyBinlogNamePosition(fileName string, position uint32) {
 	var err error
 	defer func() {
 		if err != nil {
-			utils.FileLog.Error("修改binlog文件名称和位置异常,fileName", fileName, ",position:", position, ",err:", err)
+			utils.FileLog.Error(fmt.Sprintf("修改binlog文件名称和位置异常, fileName: %s, position: %d, err: %v", fileName, position, err))
 		}
 	}()
 
@@ -300,3 +331,23 @@ func timingModifyBinlogNamePosition() {
 		}
 	}
 }
+
+// initBinlogNamePosition 初始化/重新定位binlog文件名称和位置
+func initBinlogNamePosition() (fileName string, position uint32, err error) {
+	// 获取mysql当前binlog文件名称和位置
+	status, e := binlog.GetShowMaster()
+	if e != nil {
+		err = fmt.Errorf("get mysql master status err: %v", e)
+		return
+	}
+	fileName = status.File
+	position = status.Position
+
+	// 写入redis
+	_ = utils.Rc.Put(utils.CACHE_MYSQL_DATA_FILENAME, fileName, 31*24*time.Hour)
+	_ = utils.Rc.Put(utils.CACHE_MYSQL_DATA_POSITION, position, 31*24*time.Hour)
+
+	// 更新MySQL
+	modifyBinlogNamePosition(fileName, position)
+	return
+}

+ 23 - 36
services/binlog/handler.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"reflect"
+	"runtime/debug"
 	"time"
 
 	"github.com/go-mysql-org/go-mysql/canal"
@@ -21,6 +22,15 @@ type EdbEventHandler struct {
 }
 
 func (h *EdbEventHandler) OnRow(e *canal.RowsEvent) (err error) {
+	// 下面有可能出现panic导致监听挂掉
+	defer func() {
+		if r := recover(); r != nil {
+			utils.FileLog.Error("binlog OnRow panic: ", r)
+
+			stackTrace := debug.Stack()
+			utils.FileLog.Error("binlog OnRow panic stack: ", string(stackTrace))
+		}
+	}()
 
 	// 监听逻辑
 	switch e.Action {
@@ -222,9 +232,7 @@ func (h *EdbEventHandler) MapRowToStruct(columns []schema.TableColumn, row []int
 		case "modify_time":
 			newEdbInfo.ModifyTime = value.String()
 		case "base_modify_time":
-			if value.IsValid() {
-				newEdbInfo.BaseModifyTime = value.String()
-			}
+			newEdbInfo.BaseModifyTime = value.String()
 		case "min_value":
 			newEdbInfo.MinValue = value.Float()
 		case "max_value":
@@ -273,13 +281,14 @@ func DataSourceMapRowToStruct(columns []schema.TableColumn, row []interface{}, i
 	// 根据不同数据源匹配对应的字段名
 	indexCols := indexOb.EsCols()
 	for i, column := range columns {
+		// 数据无效的话,那么就过滤掉
 		value := reflect.ValueOf(row[i])
+		if !value.IsValid() {
+			continue
+		}
 
 		switch column.Name {
 		case indexCols.PrimaryId:
-			if !value.IsValid() {
-				continue
-			}
 			if value.Kind() == reflect.Int || value.Kind() == reflect.Int32 || value.Kind() == reflect.Int64 {
 				item.PrimaryId = int(value.Int())
 			}
@@ -287,17 +296,10 @@ func DataSourceMapRowToStruct(columns []schema.TableColumn, row []interface{}, i
 				item.PrimaryId = int(value.Uint())
 			}
 		case indexCols.IndexCode:
-			if value.IsValid() {
-				item.IndexCode = value.String()
-			}
+			item.IndexCode = value.String()
 		case indexCols.IndexName:
-			if value.IsValid() {
-				item.IndexName = value.String()
-			}
+			item.IndexName = value.String()
 		case indexCols.ClassifyId:
-			if !value.IsValid() {
-				continue
-			}
 			if value.Kind() == reflect.Int || value.Kind() == reflect.Int32 || value.Kind() == reflect.Int64 {
 				item.ClassifyId = int(value.Int())
 			}
@@ -305,25 +307,14 @@ func DataSourceMapRowToStruct(columns []schema.TableColumn, row []interface{}, i
 				item.ClassifyId = int(value.Uint())
 			}
 		case indexCols.Unit:
-			if value.IsValid() {
-				item.Unit = value.String()
-			}
+			item.Unit = value.String()
 		case indexCols.Frequency:
-			if value.IsValid() {
-				item.Frequency = value.String()
-			}
+			item.Frequency = value.String()
 		case indexCols.StartDate:
-			if value.IsValid() {
-				item.StartDate = value.String()
-			}
+			item.StartDate = value.String()
 		case indexCols.EndDate:
-			if value.IsValid() {
-				item.EndDate = value.String()
-			}
+			item.EndDate = value.String()
 		case indexCols.LatestValue:
-			if !value.IsValid() {
-				continue
-			}
 			if value.Kind() == reflect.String {
 				item.LatestValue = value.String()
 			}
@@ -334,13 +325,9 @@ func DataSourceMapRowToStruct(columns []schema.TableColumn, row []interface{}, i
 				item.LatestValue = fmt.Sprint(value.Float())
 			}
 		case indexCols.CreateTime:
-			if value.IsValid() {
-				item.CreateTime = value.String()
-			}
+			item.CreateTime = value.String()
 		case indexCols.ModifyTime:
-			if value.IsValid() {
-				item.ModifyTime = value.String()
-			}
+			item.ModifyTime = value.String()
 		default:
 			continue
 		}

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

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

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

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

+ 20 - 3
services/data/chart_info.go

@@ -653,6 +653,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = ""
 			item.ChartColor = ""
 			item.ChartWidth = 0
+			item.ChartScale = 0
 			item.MaxData = v.MaxValue
 			item.MinData = v.MinValue
 		} else {
@@ -666,6 +667,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = v.ChartStyle
 			item.ChartColor = v.ChartColor
 			item.ChartWidth = v.ChartWidth
+			item.ChartScale = v.ChartScale
 			item.IsOrder = v.IsOrder
 			item.MaxData = v.MaxData
 			item.MinData = v.MinData
@@ -873,7 +875,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			if err != nil {
 				return
 			}
-
+			nowYear := time.Now().Year()
 			newDataList := make([]*data_manage.EdbDataList, 0)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -881,6 +883,10 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
+				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				year := dataTimeT.Year()
+				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				v.DataTimestamp = newItemDate.UnixNano() / 1e6
 				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
 					newDataList = append(newDataList, v)
 				}
@@ -1266,6 +1272,10 @@ func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, late
 				Value:         item.Value,
 			}
 			dataTimeT, _ := time.Parse(utils.FormatDate, item.DataTime)
+			year := dataTimeT.Year()
+			newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+			timestamp := newItemDate.UnixNano() / 1e6
+			tmpVal.DataTimestamp = timestamp
 			if (startTmpT.Before(dataTimeT) && endTmpT.After(dataTimeT)) || startTmpT == dataTimeT || endTmpT == dataTimeT {
 				tmpV := &tmpVal
 				if findVal, ok := quarterMap[name]; !ok {
@@ -2425,6 +2435,7 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 					ChartColor:        "",
 					PredictChartColor: "",
 					ChartWidth:        0,
+					ChartScale:        0,
 					Source:            utils.CHART_SOURCE_DEFAULT,
 				})
 			}
@@ -2607,6 +2618,7 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 		mapItem.ChartColor = v.ChartColor
 		mapItem.PredictChartColor = v.PredictChartColor
 		mapItem.ChartWidth = v.ChartWidth
+		mapItem.ChartScale = v.ChartScale
 		mapItem.Source = utils.CHART_SOURCE_DEFAULT
 		mapItem.EdbAliasName = v.EdbAliasName
 		mapItem.IsConvert = v.IsConvert
@@ -4289,7 +4301,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 			valueMap := make(map[time.Time]float64)
 
 			samePeriodStandardDeviationList := make([]*data_manage.MaxMinLimitsData, 0)
-			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodStandardDeviation.Year-1 && i > 0; i-- {
 				// 插值成日度
 				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
 				if err != nil {
@@ -5253,13 +5265,18 @@ func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate,
 				return
 			}
 
+			nowYear := time.Now().Year()
 			newList := make([]*data_manage.EdbDataList, 0)
-			for _, v := range newDataList {
+			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
 				if e != nil {
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
+				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				year := dataTimeT.Year()
+				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				v.DataTimestamp = newItemDate.UnixNano() / 1e6
 				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
 					newList = append(newList, v)
 				}

+ 5 - 0
services/data/chart_info_excel_balance.go

@@ -1021,6 +1021,7 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 				}
 			}
 
+			nowYear := time.Now().Year()
 			newDataList := make([]*data_manage.EdbDataList, 0)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -1028,6 +1029,10 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
+				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				year := dataTimeT.Year()
+				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				v.DataTimestamp = newItemDate.UnixNano() / 1e6
 				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
 					newDataList = append(newDataList, v)
 				}

+ 1 - 1
services/data/edb_info.go

@@ -1863,7 +1863,7 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 		edbType = 2 //计算指标
 	}
 	// 从缓存中获取
-	terminalCode, serverUrl, sourceIndexName, e := GetEdbTerminalCodeBySource(edbInfo.Source, edbInfo.EdbCode, edbInfo.StockCode)
+	terminalCode, serverUrl, sourceIndexName, e := GetEdbTerminalCodeBySource(edbInfo.Source, edbCode, edbInfo.StockCode)
 	if e != nil {
 		errMsg = "获取可以使用的终端地址失败"
 		err = errors.New("获取可以使用的终端地址失败,Err:" + e.Error())

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

@@ -108,6 +108,14 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 		SourcesFrom:        excelInfo.SourcesFrom,
 	}
 
+	// 额外配置(表格冻结行列等)
+	if excelInfo.ExtraConfig != "" {
+		if e := json.Unmarshal([]byte(excelInfo.ExtraConfig), &excelDetail.ExtraConfig); e != nil {
+			err = fmt.Errorf("额外配置解析失败, %v", e)
+			return
+		}
+	}
+
 	// 无权限,不需要返回数据
 	if !haveOperaAuth {
 		return

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

@@ -198,6 +198,7 @@ func Copy(oldExcelInfo *excelModel.ExcelInfo, excelClassifyId int, excelName str
 		IsDelete:        0,
 		ModifyTime:      time.Now(),
 		CreateTime:      time.Now(),
+		ExtraConfig:     oldExcelInfo.ExtraConfig,
 	}
 
 	// 如果不是自定义分析,那么直接加主表就好了

+ 10 - 0
services/data/predict_edb_info_rule.go

@@ -1726,6 +1726,10 @@ func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, con
 		//兼容历史数据
 		yearList = append(yearList, annualValueInversionConf.Year)
 	}
+	if len(yearList) == 0 {
+		err = errors.New("同比年份不能为空")
+		return
+	}
 	// 每年截止到当前日期的累计值
 	dateTotalMap := make(map[time.Time]float64)
 
@@ -1823,11 +1827,17 @@ func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, con
 			}
 		}
 	}
+	if sum == 0 {
+		err = errors.New("同比年份的累计值为0")
+		return
+	}
 	//fmt.Printf("同比年份的余额%.4f\n", sum)
 	avg = sum / float64(len(yearList))
 	//fmt.Printf("同比年份的余额%.4f\n", avg)
 	// 同比增速=当年余额/同比年份上一期日期的余额
+
 	tbVal := decimal.NewFromFloat(currYearBalance).Div(decimal.NewFromFloat(avg))
+
 	/*tbVal11, _ := tbVal.Round(4).Float64()
 	fmt.Printf("同比增速%.4f\n", tbVal11)*/
 	//(同比增速=余额/同比年份相应日期的余额的平均值,预测值等于同比年份同期值*同比增速);

+ 133 - 12
services/data/stl/stl.go

@@ -1,6 +1,7 @@
 package stl
 
 import (
+	"database/sql"
 	"encoding/json"
 	"errors"
 	"eta/eta_api/models/data_manage"
@@ -34,6 +35,7 @@ const (
 var EDB_DATA_CALCULATE_STL_TREND_CACHE = `eta:stl_decompose:trend:config_id:`
 var EDB_DATA_CALCULATE_STL_SEASONAL_CACHE = `eta:stl_decompose:seasonal:config_id:`
 var EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE = `eta:stl_decompose:residual:config_id:`
+var EDB_DATA_CALCULATE_STL_NonTrend_CACHE = `eta:stl_decompose:non_trend:config_id:`
 
 func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.StlPreviewResp, msg string, err error) {
 	config, err := stl.GetCalculateStlConfigById(req.CalculateStlConfigId)
@@ -164,7 +166,7 @@ func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.
 		msg = "计算失败,请重新选择指标和参数后计算"
 		return
 	}
-	trendChart, seasonalChart, residualChart, err := ParseStlExcel(saveFilePath)
+	trendChart, seasonalChart, residualChart, nonTrendChartInfo, err := ParseStlExcel(saveFilePath)
 	if err != nil {
 		msg = "解析Excel失败"
 		return
@@ -200,6 +202,17 @@ func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.
 	resp.ResidualChartInfo.Title = edbInfo.EdbName + "Residual"
 	resp.ResidualChartInfo.Frequency = edbInfo.Frequency
 	resp.ResidualChartInfo.Unit = edbInfo.Unit
+
+	// 季节性项+残差项
+	nonTrendName := fmt.Sprintf("%sNon-Trend/F%g", edbInfo.EdbName, confReq.Fraction)
+	resp.NonTrendChartInfo.DataList = nonTrendChartInfo.DataList
+	resp.NonTrendChartInfo.MaxData = nonTrendChartInfo.MaxData
+	resp.NonTrendChartInfo.MinData = nonTrendChartInfo.MinData
+	resp.NonTrendChartInfo.ClassifyId = edbInfo.ClassifyId
+	resp.NonTrendChartInfo.Title = nonTrendName
+	resp.NonTrendChartInfo.Frequency = edbInfo.Frequency
+	resp.NonTrendChartInfo.Unit = edbInfo.Unit
+
 	resp.EvaluationResult.Mean = strconv.FormatFloat(result.ResidualMean, 'f', 4, 64)
 	resp.EvaluationResult.Std = strconv.FormatFloat(result.ResidualVar, 'f', 4, 64)
 	resp.EvaluationResult.AdfPValue = strconv.FormatFloat(result.AdfPValue, 'f', -1, 64)
@@ -213,15 +226,18 @@ func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.
 	var relationEdbInfoId []int
 	for _, mapping := range confMapping {
 		switch mapping.StlEdbType {
-		case 1:
+		case utils.StlTypeTrend:
 			resp.TrendChartInfo.EdbInfoId = mapping.EdbInfoId
 			relationEdbInfoId = append(relationEdbInfoId, mapping.EdbInfoId)
-		case 2:
+		case utils.StlTypeSeasonal:
 			resp.SeasonalChartInfo.EdbInfoId = mapping.EdbInfoId
 			relationEdbInfoId = append(relationEdbInfoId, mapping.EdbInfoId)
-		case 3:
+		case utils.StlTypeResidual:
 			resp.ResidualChartInfo.EdbInfoId = mapping.EdbInfoId
 			relationEdbInfoId = append(relationEdbInfoId, mapping.EdbInfoId)
+		case utils.StlTypeNonTrend:
+			resp.NonTrendChartInfo.EdbInfoId = mapping.EdbInfoId
+			relationEdbInfoId = append(relationEdbInfoId, mapping.EdbInfoId)
 		}
 	}
 	relationEdbInfo, err := data_manage.GetEdbInfoByIdList(relationEdbInfoId)
@@ -246,12 +262,18 @@ func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.
 			resp.ResidualChartInfo.ClassifyId = info.ClassifyId
 			resp.ResidualChartInfo.Frequency = info.Frequency
 			resp.ResidualChartInfo.Unit = info.Unit
+		case resp.NonTrendChartInfo.EdbInfoId:
+			resp.NonTrendChartInfo.Title = info.EdbName
+			resp.NonTrendChartInfo.ClassifyId = info.ClassifyId
+			resp.NonTrendChartInfo.Frequency = info.Frequency
+			resp.NonTrendChartInfo.Unit = info.Unit
 		}
 	}
 
 	bTrend, _ := json.Marshal(trendChart.DataList)
 	bSeasonal, _ := json.Marshal(seasonalChart.DataList)
 	bResidual, _ := json.Marshal(residualChart.DataList)
+	bNonTrend, _ := json.Marshal(nonTrendChartInfo.DataList)
 	err = utils.Rc.Put(EDB_DATA_CALCULATE_STL_TREND_CACHE+strconv.Itoa(config.CalculateStlConfigId), bTrend, time.Hour*2)
 	if err != nil {
 		msg = "计算失败,请重新计算"
@@ -262,7 +284,11 @@ func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.
 		msg = "计算失败,请重新计算"
 		return
 	}
-	utils.Rc.Put(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE+strconv.Itoa(config.CalculateStlConfigId), bResidual, time.Hour*2)
+	err = utils.Rc.Put(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE+strconv.Itoa(config.CalculateStlConfigId), bResidual, time.Hour*2)
+	if err != nil {
+		msg = "计算失败,请重新计算"
+	}
+	err = utils.Rc.Put(EDB_DATA_CALCULATE_STL_NonTrend_CACHE+strconv.Itoa(config.CalculateStlConfigId), bNonTrend, time.Hour*2)
 	if err != nil {
 		msg = "计算失败,请重新计算"
 	}
@@ -289,7 +315,7 @@ func CheckOsPathAndMake(path string) (err error) {
 	return
 }
 
-func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart response.ChartEdbInfo, err error) {
+func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart, nonTrendChartInfo response.ChartEdbInfo, err error) {
 	file, err := xlsx.OpenFile(excelPath)
 	if err != nil {
 		return
@@ -388,6 +414,68 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
 			ResidualChart.MinData = MinData
 		}
 	}
+
+	// 数据处理
+	dateList := make([]string, 0)
+	residualDateMap := make(map[string]*response.EdbData)
+	for _, item := range ResidualChart.DataList {
+		if _, ok := residualDateMap[item.DataTime]; ok {
+			continue
+		}
+		residualDateMap[item.DataTime] = item
+		dateList = append(dateList, item.DataTime)
+	}
+	seasonalDateMap := make(map[string]*response.EdbData)
+	for _, item := range SeasonalChart.DataList {
+		if _, ok := seasonalDateMap[item.DataTime]; ok {
+			continue
+		}
+		seasonalDateMap[item.DataTime] = item
+	}
+
+	// 季节性项+残差项
+	{
+		dataList := make([]*response.EdbData, 0)
+		var minValue, maxValue sql.NullFloat64
+		for _, date := range dateList {
+			tmpResidual, ok := residualDateMap[date]
+			if !ok {
+				continue
+			}
+			tmpSeasonal, ok := seasonalDateMap[date]
+			if !ok {
+				continue
+			}
+
+			tmpValue := tmpResidual.Value + tmpSeasonal.Value
+			tmpValue, _ = decimal.NewFromFloat(tmpValue).Round(4).Float64()
+
+			dataList = append(dataList, &response.EdbData{
+				DataTime:      date,
+				DataTimestamp: 0,
+				Value:         tmpValue,
+			})
+			// 如果没有设置最小值,或者设置的最小值比当前值还大,则需要更新最小值
+			if !minValue.Valid || minValue.Float64 > tmpValue {
+				err = minValue.Scan(tmpValue)
+				if err != nil {
+					return
+				}
+			}
+			// 如果没有设置最大值,或者设置的最大值比当前值还小,则需要更新最大值
+			if !maxValue.Valid || maxValue.Float64 < tmpValue {
+				err = maxValue.Scan(tmpValue)
+				if err != nil {
+					return
+				}
+			}
+		}
+
+		nonTrendChartInfo.DataList = dataList
+		nonTrendChartInfo.MinData = minValue.Float64
+		nonTrendChartInfo.MaxData = maxValue.Float64
+	}
+
 	return
 }
 
@@ -823,7 +911,7 @@ func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName,
 	}
 	var edbInfoData []*response.EdbData
 	switch req.StlEdbType {
-	case 1:
+	case utils.StlTypeTrend:
 		// 趋势指标
 		if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_TREND_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
 			msg = "计算已过期,请重新计算"
@@ -841,7 +929,7 @@ func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName,
 			err = fmt.Errorf("json解析失败,Err:" + er.Error())
 			return
 		}
-	case 2:
+	case utils.StlTypeSeasonal:
 		// 季节性指标
 		if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_SEASONAL_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
 			msg = "计算已过期,请重新计算"
@@ -859,7 +947,7 @@ func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName,
 			err = fmt.Errorf("json解析失败,Err:" + er.Error())
 			return
 		}
-	case 3:
+	case utils.StlTypeResidual:
 		// 残差性指标
 		if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
 			msg = "计算已过期,请重新计算"
@@ -877,6 +965,24 @@ func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName,
 			err = fmt.Errorf("json解析失败,Err:" + er.Error())
 			return
 		}
+	case utils.StlTypeNonTrend:
+		// 残差性指标
+		if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_NonTrend_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
+			msg = "计算已过期,请重新计算"
+			err = fmt.Errorf("not found")
+			return
+		}
+		nonTrendData, er := utils.Rc.RedisBytes(EDB_DATA_CALCULATE_STL_NonTrend_CACHE + strconv.Itoa(req.CalculateStlConfigId))
+		if er != nil {
+			msg = "获取失败"
+			err = fmt.Errorf("获取redis数据失败,Err:" + er.Error())
+			return
+		}
+		if er := json.Unmarshal(nonTrendData, &edbInfoData); er != nil {
+			msg = "获取失败"
+			err = fmt.Errorf("json解析失败,Err:" + er.Error())
+			return
+		}
 	default:
 		msg = "获取失败"
 		err = fmt.Errorf("未知的计算类型")
@@ -1187,7 +1293,7 @@ func SyncUpdateRelationEdbInfo(configId int, excludeId int) (err error) {
 		}
 		var edbInfoData []*response.EdbData
 		switch v.StlEdbType {
-		case 1:
+		case utils.StlTypeTrend:
 			// 趋势指标
 			if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_TREND_CACHE + strconv.Itoa(v.CalculateStlConfigId)); !ok {
 				utils.FileLog.Info(EDB_DATA_CALCULATE_STL_TREND_CACHE + strconv.Itoa(v.CalculateStlConfigId) + "指标数据不存在")
@@ -1202,7 +1308,7 @@ func SyncUpdateRelationEdbInfo(configId int, excludeId int) (err error) {
 				utils.FileLog.Info("redis获取解析, body:%s,err:%s", string(trendData), er.Error())
 				continue
 			}
-		case 2:
+		case utils.StlTypeSeasonal:
 			// 季节性指标
 			if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_SEASONAL_CACHE + strconv.Itoa(v.CalculateStlConfigId)); !ok {
 				utils.FileLog.Info(EDB_DATA_CALCULATE_STL_SEASONAL_CACHE + strconv.Itoa(v.CalculateStlConfigId) + "指标数据不存在")
@@ -1217,7 +1323,7 @@ func SyncUpdateRelationEdbInfo(configId int, excludeId int) (err error) {
 				utils.FileLog.Info("redis数据解析失败, body:%s,err:%s", string(seasonalData), er.Error())
 				continue
 			}
-		case 3:
+		case utils.StlTypeResidual:
 			// 残差性指标
 			if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE + strconv.Itoa(v.CalculateStlConfigId)); !ok {
 				utils.FileLog.Info(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE + strconv.Itoa(v.CalculateStlConfigId) + "指标数据不存在")
@@ -1232,6 +1338,21 @@ func SyncUpdateRelationEdbInfo(configId int, excludeId int) (err error) {
 				utils.FileLog.Info("redis数据解析失败, body:%s,err:%s", string(residualData), er.Error())
 				continue
 			}
+		case utils.StlTypeNonTrend:
+			// 残差性指标
+			if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_NonTrend_CACHE + strconv.Itoa(v.CalculateStlConfigId)); !ok {
+				utils.FileLog.Info(EDB_DATA_CALCULATE_STL_NonTrend_CACHE + strconv.Itoa(v.CalculateStlConfigId) + "指标数据不存在")
+				continue
+			}
+			nonTrendData, er := utils.Rc.RedisBytes(EDB_DATA_CALCULATE_STL_NonTrend_CACHE + strconv.Itoa(v.CalculateStlConfigId))
+			if er != nil {
+				utils.FileLog.Info(EDB_DATA_CALCULATE_STL_NonTrend_CACHE + strconv.Itoa(v.CalculateStlConfigId) + "redis获取失败,err:" + er.Error())
+				continue
+			}
+			if er := json.Unmarshal(nonTrendData, &edbInfoData); er != nil {
+				utils.FileLog.Info("redis数据解析失败, body:%s,err:%s", string(nonTrendData), er.Error())
+				continue
+			}
 		default:
 			utils.FileLog.Info("未知的stlEdbType类型, mapping:%v", v)
 			continue

+ 16 - 1
services/report_v2.go

@@ -1766,6 +1766,9 @@ func handleTableLinkReportId(link string, reportId, fromScene int) string {
 			utils.FileLog.Info("处理链接失败,ERR:" + err.Error())
 		}
 	}()
+	if link == `` {
+		return link
+	}
 	parsedURL, err := url.Parse(link)
 	if err != nil {
 		return link
@@ -1820,7 +1823,8 @@ func HandleReportContentStructTable(reportId int, body string) (newBody string)
 	}
 
 	// 将处理后的数据转换回JSON字符串
-	modifiedJSON, err := json.MarshalIndent(jsonData, "", "  ")
+	//modifiedJSON, err := json.MarshalIndent(jsonData, "", "  ")
+	modifiedJSON, err := json.Marshal(jsonData)
 	if err != nil {
 		fmt.Println("Error marshaling JSON:", err)
 		return
@@ -1875,6 +1879,17 @@ func processMapTable(data map[string]interface{}, reportId, fromScene int) error
 		switch v := value.(type) {
 		case string:
 			if key == "content" {
+				contentSource, ok := data["compType"]
+				if !ok {
+					continue
+				}
+				contentSourceType, ok := contentSource.(string)
+				if !ok {
+					continue
+				}
+				if !utils.InArrayByStr([]string{`sheet`, `chart`}, contentSourceType) {
+					continue
+				}
 				newContent := handleTableLinkReportId(v, reportId, fromScene)
 				data[key] = newContent
 			}

+ 9 - 0
utils/constants.go

@@ -341,6 +341,7 @@ const (
 const (
 	CHART_TYPE_CURVE           = 1  //曲线图
 	CHART_TYPE_SEASON          = 2  //季节性图
+	CHART_TYPE_AREA            = 3  // 面积图
 	CHART_TYPE_BAR             = 7  //柱形图
 	CHART_TYPE_SECTION_SCATTER = 10 //截面散点图样式
 	CHART_TYPE_RADAR           = 11 //雷达图
@@ -533,6 +534,14 @@ const (
 	TableReferencedByEnPPT    = 5 // 英文PPT 与表格的关系
 )
 
+// STL模型类型
+const (
+	StlTypeTrend    = 1 // 趋势
+	StlTypeSeasonal = 2 // 季节性指标
+	StlTypeResidual = 3 // 残差性指标
+	StlTypeNonTrend = 4 // 非趋势性指标
+)
+
 // 基础数据初始化日期
 var (
 	BASE_START_DATE         = `1900-01-01`                                          //基础数据开始日期

+ 8 - 1
utils/date_util.go

@@ -42,7 +42,8 @@ func GetNextDay(date string) string {
 
 // GetNextDayN 获取 yyyy-MM-dd 类型的时间的下n天
 func GetNextDayN(date string, n int) string {
-	t, _ := time.Parse("2006-01-02", date)
+	t, _ := time.ParseInLocation("2006-01-02", date, time.Local)
+	//t, _ := time.Parse("2006-01-02", date)
 	nextDay := t.AddDate(0, 0, n)
 	return nextDay.Format("2006-01-02")
 }
@@ -115,6 +116,12 @@ func StringToTime(date string) time.Time {
 	return t
 }
 
+// StringToFormatTime string 类型时间 转换为指定格式的 time.Time类型
+func StringToFormatTime(data string, format string) time.Time {
+	t, _ := time.ParseInLocation(format, data, time.Local)
+	return t
+}
+
 // TimeToString time.Time 类型时间 转换为 string 类型
 func TimeToString(t time.Time, format string) string {
 	formattedTime := t.Format(format)