jwyu %!s(int64=2) %!d(string=hai) anos
pai
achega
0eaf33147e

+ 10 - 0
package-lock.json

@@ -554,6 +554,11 @@
         "function-bind": "^1.1.1"
       }
     },
+    "highcharts": {
+      "version": "10.3.3",
+      "resolved": "https://registry.npmmirror.com/highcharts/-/highcharts-10.3.3.tgz",
+      "integrity": "sha512-r7wgUPQI9tr3jFDn3XT36qsNwEIZYcfgz4mkKEA6E4nn5p86y+u1EZjazIG4TRkl5/gmGRtkBUiZW81g029RIw=="
+    },
     "immutable": {
       "version": "4.3.0",
       "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.0.tgz",
@@ -661,6 +666,11 @@
         "brace-expansion": "^2.0.1"
       }
     },
+    "moment": {
+      "version": "2.29.4",
+      "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz",
+      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+    },
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz",

+ 2 - 0
package.json

@@ -10,8 +10,10 @@
   },
   "dependencies": {
     "axios": "^1.3.4",
+    "highcharts": "^10.3.3",
     "js-base64": "^3.7.5",
     "js-md5": "^0.7.3",
+    "moment": "^2.29.4",
     "normalize.css": "^8.0.1",
     "vant": "^4.1.2",
     "vue": "^3.2.47",

+ 9 - 0
src/api/chart.js

@@ -0,0 +1,9 @@
+import { get,post } from "./index";
+
+/**
+ * 通过code获取图表详情
+ * @param UniqueCode 
+ */
+export function apiChartInfoByCode(params){
+    return get('/datamanage/chart_info/common/detail/from_unique_code',params)
+}

+ 18 - 2
src/api/index.js

@@ -1,6 +1,7 @@
 "use strict";
 import axios from "axios";
-import { showLoadingToast,showToast,closeToast   } from "vant";
+import router from "@/router";
+import { showLoadingToast,showToast,closeToast } from "vant";
 
 // Full config:  https://github.com/axios/axios#request-config
 // axios.defaults.baseURL = process.env.baseURL || process.env.apiUrl || '';
@@ -51,7 +52,22 @@ _axios.interceptors.response.use(
       closeToast()
     }
 
-    if(![200,403,4003,4002].includes(data.Ret)){
+    if(response.status!==200){
+      setTimeout(() => {
+        showToast('网络异常')
+      }, 100);
+    }
+
+    if(!data){
+      setTimeout(() => {
+        showToast('服务器开了个小差')
+      }, 100);
+    }
+    if(data.Ret===408){//token失效
+      showToast(data.Msg)
+      router.replace('/login')
+    }
+    if(data.Ret===403){
       setTimeout(() => {
         showToast(data.Msg)
       }, 100);

+ 20 - 0
src/api/ppt.js

@@ -0,0 +1,20 @@
+/**
+ * 中文ppt模块
+ */
+
+import {get,post} from './index'
+
+/**
+ * ppt分类数据
+ */
+export function apiPPTClassify(){
+    return get('/pptv2/groups',{})
+}
+
+/**
+ * 获取ppt详情
+ * @param PptId
+ */
+export function apiPPTDetail(params){
+    return get('/pptv2/detail',params)
+}

+ 9 - 0
src/assets/styles/common.scss

@@ -0,0 +1,9 @@
+
+html,body{
+    font-size: 14px;
+    color: #333;
+}
+
+div{
+    box-sizing: border-box;
+}

+ 40 - 0
src/components/chart/Index.vue

@@ -0,0 +1,40 @@
+<script setup>
+import {watch,onMounted} from 'vue'
+import {apiChartInfoByCode} from '@/api/chart.js'
+import {chartRender} from './utils/render'
+
+const props=defineProps({
+    code:{
+        type:String,
+        default:''
+    },
+    renderId:{//渲染到的dom id
+        type:String,
+        default:''
+    }
+})
+
+
+onMounted(()=>{
+    getChartInfo()
+})
+// 获取图表详情
+async function getChartInfo(){
+    const res=await apiChartInfoByCode({UniqueCode:props.code})
+    if(res.Ret===200){
+        chartRender(res.Data,props.renderId)
+    }
+}
+
+</script>
+
+<template>
+    <div class="chart-box" :id="renderId"></div>
+</template>
+
+<style lang="scss" scoped>
+.chart-box{
+    width: 100%;
+    height: 100%;
+}
+</style>

+ 152 - 0
src/components/chart/utils/config.js

@@ -0,0 +1,152 @@
+
+//图模块公共配置
+
+// 图默认配置
+export const chartDefaultOpts={
+    //图表配置
+	chart: {
+		spacingTop: 30,
+		backgroundColor: "rgba(0,0,0,0)",
+	},
+	title: {
+		enabled: false
+	},
+	exporting: {
+		enabled: false,
+	},
+	//默认颜色配置
+	colors:['#00f','#f00','#999','#000','#7cb5ec', '#90ed7d', '#f7a35c', '#8085e9', 
+	'#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1'],
+	//版权信息
+	credits: {enabled:false},
+	//数据列通用配置
+	plotOptions: {
+		series: {
+			turboThreshold: 0, //不限制数据点个数 
+			boostThreshold:0,
+			animation: {
+				duration: 1000
+			}
+		},
+		areaspline: {
+			lineWidth: 1,
+			stacking: 'normal',
+			marker: {
+					enabled: false,
+			},
+			// fillOpacity: 0.5,
+		},
+		column:{
+			pointPadding: 0.05,
+			stacking: 'normal',
+		},
+		scatter: {
+			turboThreshold: 0,
+			marker: {
+					symbol: 'circle',
+					radius: 5,
+					states: {
+							hover: {
+									enabled: true,
+							}
+					}
+			},
+			states: {
+				hover: {
+					marker: {
+						enabled: true
+					}
+				}
+			}
+		}
+	},
+	//范围选择器
+	rangeSelector: {
+		enabled: false,
+		selected: 2,
+	},
+	//悬浮提示框
+	tooltip: {
+		split: false,
+		shared: true,
+		dateTimeLabelFormats: {
+			// 时间格式化字符
+			day: '%Y/%m/%d',
+			week: "%Y/%m",
+			month: '%Y/%m',
+			year: '%Y/%m',
+		},
+		xDateFormat:'%Y/%m/%d',
+		className:'chart-tooltips-box'
+		// formatter:function(e){
+		// 	return `<p>${this.x}</p><p>aaa</p>`
+		// 	// return e
+		// }
+		// outside: true,
+		// valueDecimals: 4,
+	},
+	//图例
+	legend: {
+		enabled: false,
+		verticalAlign: 'top',
+		margin:3,
+		// layout: 'vertical'
+	},
+	//滚动条
+	scrollbar: {
+		enabled: false,
+	},
+	//导航器
+	navigator: {
+		enabled: false,
+	},
+	//范围选择器
+	rangeSelector: {
+		enabled: false,
+	},
+}
+
+// 散点x轴
+export const scatterXAxis = {
+    tickPosition: 'inside',
+    lineColor: '#bfbfbf',
+    tickColor: '#bfbfbf',
+    tickLength:5,
+    ordinal: false,
+    type: 'linear',
+}
+
+// 基础y轴配置
+export const basicYAxis = {
+    tickWidth: 1,
+    tickLength: 5,
+    lineWidth: 1,
+    lineColor: '#bfbfbf',
+    tickColor: '#bfbfbf',
+    // offset: 0,
+    visible: true,
+    gridLineWidth: 0,
+    tickPosition: 'inside',
+    endOnTick: false,
+    startOnTick: false,
+    showLastLabel: true,
+    tickPixelInterval: 50,
+}
+
+//基础x轴配置
+export const basicXAxis={
+    tickPosition: 'inside',
+    lineColor: '#bfbfbf',
+    tickColor: '#bfbfbf',
+    tickLength:5,
+    type: 'datetime',
+    ordinal: false,
+    dateTimeLabelFormats: {
+        day: '%y/%m',
+        week: '%y/%m',
+        month: '%y/%m',
+        year: '%y/%m',
+    },
+    xDateFormat:'%Y-%m-%d'
+}
+

+ 240 - 0
src/components/chart/utils/highcahrts-zh_CN.js

@@ -0,0 +1,240 @@
+/**
+ * Highcharts-zh_CN ES6 版本 plugins v1.2.1 (2020-07-29)
+ *
+ * (c) 2020 Jianshu Technology Co.,LTD (https://jianshukeji.com)
+ *
+ * Author : john@jianshukeji.com, Blue Monkey
+ *
+ * License: Creative Commons Attribution (CC)
+ */
+
+export default (H) => {
+  let protocol = window.location.protocol;
+
+  if (!/^http(s)?:&/.test(protocol)) {
+    protocol = 'http:';
+  }
+
+  let defaultOptionsZhCn = {
+    lang: {
+      // Highcharts
+      contextButtonTitle: '图表导出菜单',
+      decimalPoint: '.',
+      downloadJPEG: '下载 JPEG 图片',
+      downloadPDF: '下载 PDF 文件',
+      downloadPNG: '下载 PNG 文件',
+      downloadSVG: '下载 SVG 文件',
+      downloadXLS: '下载 XLS 文件',
+      drillUpText: '◁ 返回 {series.name}',
+      exitFullscreen: '退出全屏',
+      exportData: {
+        categoryDatetimeHeader: '时间',
+        categoryHeader: '类别',
+      },
+      openInCloud: '在 Highcharts Cloud 中打开',
+      invalidDate: '无效的时间',
+      loading: '加载中...',
+      months: [
+        '一月',
+        '二月',
+        '三月',
+        '四月',
+        '五月',
+        '六月',
+        '七月',
+        '八月',
+        '九月',
+        '十月',
+        '十一月',
+        '十二月',
+      ],
+      navigation: {
+        popup: {
+          addButton: '新增',
+          arrowLine: '直线',
+          arrowRay: '射线',
+          arrowSegment: '线段',
+          background: '背景',
+          backgroundColor: '背景颜色',
+          backgroundColors: '背景颜色',
+          borderColor: '边框颜色',
+          borderRadius: '圆角',
+          borderWidth: '边框大小',
+          circle: '圆',
+          color: '颜色',
+          connector: '连接',
+          crooked3: 'Crooked 3 line',
+          crooked5: 'Crooked 5 line',
+          crosshairX: '竖直准星线',
+          crosshairY: '水平准星线',
+          editButton: '编辑',
+          elliott3: 'Elliott 3 line',
+          elliott5: 'Elliott 5 line',
+          fibonacci: '斐波纳契',
+          fill: '填充颜色',
+          flags: '标志',
+          fontSize: '字体大小',
+          format: '文本',
+          height: '高度',
+          horizontalLine: '水平线',
+          infinityLine: '无限线',
+          innerBackground: '内背景',
+          label: '文字标签',
+          labelOptions: '文字标签配置',
+          labels: '文字标签',
+          line: '线',
+          lines: '线条',
+          measure: 'Measure',
+          measureX: 'Measure X',
+          measureXY: 'Measure XY',
+          measureY: 'Measure Y',
+          name: '名字',
+          outerBackground: '外背景',
+          padding: '内间距',
+          parallelChannel: '并行通道',
+          pitchfork: '杈子',
+          ray: '射线',
+          rectangle: '矩形',
+          removeButton: '删除',
+          saveButton: '保存',
+          segment: '段落',
+          series: '数据列',
+          shapeOptions: '图形配置',
+          shapes: '图形',
+          simpleShapes: '简单图形',
+          stroke: '线条颜色',
+          strokeWidth: '线条粗细',
+          style: '样式',
+          title: '标题',
+          tunnel: '通道',
+          typeOptions: '详情',
+          verticalArrow: '竖直箭头',
+          verticalCounter: '竖直计数器',
+          verticalLabel: '竖直标签',
+          verticalLine: '竖直线',
+          volume: '成交量',
+        },
+      },
+      noData: '暂无数据',
+      numericSymbols: null,
+      printChart: '打印图表',
+      resetZoom: '重置缩放比例',
+      resetZoomTitle: '重置为原始大小',
+      shortMonths: [
+        '一月',
+        '二月',
+        '三月',
+        '四月',
+        '五月',
+        '六月',
+        '七月',
+        '八月',
+        '九月',
+        '十月',
+        '十一月',
+        '十二月',
+      ],
+      thousandsSep: ',',
+      viewData: '查看数据表格',
+      viewFullscreen: '全屏查看',
+      weekdays: [
+        '星期天',
+        '星期一',
+        '星期二',
+        '星期三',
+        '星期四',
+        '星期五',
+        '星期六',
+      ],
+      viewData: '查看数据表格',
+      // Highstock
+      rangeSelectorFrom: '开始时间',
+      rangeSelectorTo: '结束时间',
+      rangeSelectorZoom: '范围',
+
+      // Highmaps
+      zoomIn: '缩小',
+      zoomOut: '放大',
+    },
+
+    global: {
+      // 不使用 UTC时间
+      useUTC: false,
+      timezoneOffset: -8 * 60,
+      canvasToolsURL:
+        protocol + '//cdn.hcharts.cn/highcharts/modules/canvas-tools.js',
+      VMLRadialGradientURL:
+        protocol + +'//cdn.hcharts.cn/highcharts/gfx/vml-radial-gradient.png',
+    },
+
+    exporting: {
+      url: protocol + '//export.highcharts.com.cn',
+    },
+
+    credits: {
+      text: 'Highcharts.com.cn',
+      href: 'https://www.highcharts.com.cn',
+    },
+
+    /**
+     * Highstock
+     */
+
+    rangeSelector: {
+      inputDateFormat: '%Y-%m-%d',
+      buttons: [
+        {
+          type: 'month',
+          count: 1,
+          text: '月',
+        },
+        {
+          type: 'month',
+          count: 3,
+          text: '季度',
+        },
+        {
+          type: 'month',
+          count: 6,
+          text: '半年',
+        },
+        {
+          type: 'ytd',
+          text: 'YTD',
+        },
+        {
+          type: 'year',
+          count: 1,
+          text: '年',
+        },
+        {
+          type: 'all',
+          text: '所有',
+        },
+      ],
+    },
+
+    plotOptions: {
+      series: {
+        dataGrouping: {
+          dateTimeLabelFormats: {
+            /* millisecond: [
+              '%Y-%m-%d %H:%M:%S.%L',
+              '%Y-%m-%d %H:%M:%S.%L',
+              ' ~ %H:%M:%S.%L',
+            ],
+            second: ['%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M:%S', ' ~ %H:%M:%S'],
+            minute: ['%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M', ' ~ %H:%M'],
+            hour: ['%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M', ' ~ %H:%M'], */
+            day: ['%Y/%m/%d', '%Y/%m/%d', ' ~ %Y/%m/%d'],
+            week: ['%Y/%m/%d', '%Y/%m/%d', ' ~ %Y/%m/%d'],
+            month: ['%Y/%m', '%Y/%m', ' ~ %Y/%m'],
+            year: ['%Y', '%Y', ' ~ %Y'],
+          },
+        },
+      },
+    },
+  };
+
+  H.setOptions(defaultOptionsZhCn);
+};

+ 1134 - 0
src/components/chart/utils/render.js

@@ -0,0 +1,1134 @@
+// 图渲染逻辑模块
+
+import {onMounted,ref} from 'vue'
+import {chartDefaultOpts,scatterXAxis,basicYAxis,basicXAxis} from './config'
+import Highcharts from 'highcharts/highstock';
+import HighchartszhCN  from './highcahrts-zh_CN.js'
+import moment from 'moment'
+HighchartszhCN(Highcharts)
+
+/**
+ * 渲染图方法
+ * @param data 图详情数据
+ * @param renderId 图表在dom中的id
+ */
+export function chartRender(data,renderId){
+    let obj={...chartDefaultOpts}
+    let chartOpt={}
+    if([1,3,4,5,6].includes(data.ChartInfo.ChartType)){
+        const chartSetMap = {
+            1: setSplineOpt,
+            3: setStackOrCombinChart,
+            4: setStackOrCombinChart,
+            5: setScatterOptions,
+            6: setStackOrCombinChart
+        };
+        chartOpt=chartSetMap[data.ChartInfo.ChartType](data)
+    }else if(data.ChartInfo.ChartType ===2 ) {
+        chartOpt=setSeasonOpt(data.EdbInfoList[0])
+    }else if(data.ChartInfo.ChartType ===7){//奇怪柱形图依赖数据
+        chartOpt=initBarData(data);
+    }else if(data.ChartInfo.ChartType ===8){//商品价格曲线
+        chartOpt=initCommodityData(data);
+    }else if(data.ChartInfo.ChartType ===9){//相关性图
+        chartOpt=initRelevanceChart(data);
+    }else if(data.ChartInfo.ChartType ===10){//截面散点图
+        chartOpt=initSectionScatterData(data);
+    }
+    obj={...obj,...chartOpt}
+
+    console.log(obj);
+
+    //stock不支持线形图只支持时间图 某些用chart
+    let is_linear = chartOpt.series 
+    ? chartOpt.series.every(_ => _.type === 'scatter') || chartOpt.series.some(_ => _.chartType === 'linear')
+    : false ;
+    is_linear ?Highcharts.chart(renderId,obj) : Highcharts.stockChart(renderId,obj);
+}
+
+
+/* 预测配置 分区 */
+function getPredictParams ({LatestDate,PredictChartColor}) {
+    return {
+    zoneAxis: 'x',
+    zones: [{
+        value: new Date(LatestDate).getTime()+1
+    }, {
+        dashStyle: 'ShortDot',
+        color: PredictChartColor
+    }]
+    }
+}
+
+/* 季节图预测数据 年份=分割点年份做分割 年份>分割点年份全为预测 */
+function getSeasonPredictParams (timestamp){
+    return timestamp
+    ? {
+        zoneAxis: 'x',
+        zones: [{
+            value: new Date(timestamp).getTime()+1
+        }, {
+            dashStyle: 'ShortDot',
+        }]
+        }
+    : {}
+}
+
+// 查询范围为1年内 x轴显示为月/日 否则默认年/月
+function xTimeDiffer(minTime,maxTime){
+    //年限差
+    let year_differ=moment(maxTime).diff(moment(minTime),'years',true)
+    console.log('年限差',year_differ)
+    if (year_differ<=1) {
+        console.log('true');
+        return true;
+    } else {
+        console.log('false');
+        return false;
+    }
+}
+
+/* 拼接数据列动态name */
+function setDyncmicSerieName (item,dynamic_arr) {
+    const { IsAxis,IsOrder,EdbInfoType,LeadValue,LeadUnit }  = item;
+    // IsAxis左轴1 右轴0 2右2轴 
+      //IsOrder正序false 逆序true 
+      //EdbInfoType是否是领先指标
+    const axisLabelMap = {
+        0: '右轴',
+        2: '右2轴'
+    }
+    const orderLabelMap = {
+        1: '逆序'
+    }
+    const edbInfoMap = {
+        0: '领先'
+    }
+
+    let axis_tag = axisLabelMap[IsAxis] || '';
+      //逆序拼接
+    let order_tag = orderLabelMap[Number(IsOrder)] ? `${axis_tag ? ',': ''}${orderLabelMap[Number(IsOrder)]}` : ''
+      //领先拼接
+    let edb_tag = edbInfoMap[EdbInfoType] ? `${(axis_tag||order_tag) ? ',' : '' }${edbInfoMap[EdbInfoType]}${LeadValue}${LeadUnit}` : '';
+
+    let dynamic_tag = (axis_tag || order_tag || edb_tag) ? `(${axis_tag}${order_tag}${edb_tag})` : '';
+
+    let temName = dynamic_arr.length > 1
+                ? `${item.EdbName}(${item.SourceName})${dynamic_tag}`
+                : `${item.EdbName}${dynamic_tag}`
+    if(temName.length>20){
+        let temArr=[]
+        for(let i=0;i<temName.length/20;i++){
+            temArr.push(temName.slice(i*20,i*20+20))
+        }
+        // console.log(temArr);
+        temName=temArr.join('<br>')
+    }
+
+    return temName
+}
+
+
+//曲线图
+function setSplineOpt(e){
+    const data=e.EdbInfoList
+    let series=[]
+    let yAxis=[]
+    let xAxis = {}
+
+    let temYLeftArr=[]
+    let temYRightArr=[]
+    let temYRightTwoArr = []
+    let temYLeftIndex=data.findIndex((item) => item.IsAxis===1)
+    let temYRightIndex=data.findIndex((item) => !item.IsAxis)
+    let temYRightTwoIndex = data.findIndex((item) => item.IsAxis===2)
+
+    let minAndMaxTimeTemArr=[]//存放所有指标的最大最小时间
+
+    //有右二轴时排个序 按照左 右 右2的顺序
+    let newData = data.some(_ =>_.IsAxis===2) ? changeEdbOrder(data) : data;
+
+    newData.forEach((item,index)=>{
+
+         //轴位置值相同的下标
+        let sameSideIndex = newData.findIndex(i => i.IsAxis === item.IsAxis);
+
+        let dynamic_title = item.EdbName;
+        let dynamic_arr = newData.filter(
+          (item) => dynamic_title === item.EdbName
+        );
+        //处理数据列name
+        let temName= setDyncmicSerieName(item,dynamic_arr)  
+
+        //预测指标配置
+        let predict_params = item.EdbInfoCategoryType === 1 ? getPredictParams(item) : {};
+
+        let seriesItemObj={
+            data:[],
+            dataGrouping:{
+                enabled:false
+            },
+            type: 'spline',
+            yAxis:index,
+            name:temName,
+            color: item.ChartColor,
+            lineWidth: Number(item.ChartWidth),
+            visible:true,
+            LatestDate:item.LatestDate,
+            LatestValue:item.LatestValue,
+            ...predict_params
+        }
+        item.DataList = item.DataList || [];
+        for (let i of item.DataList) {
+          seriesItemObj.data.push([i.DataTimestamp, i.Value]);
+        }
+        series.push(seriesItemObj)
+        
+
+        // 设置y轴
+        if(item.IsAxis){
+            temYLeftArr.push(item)
+        }else{
+            temYRightArr.push(item)
+        }
+
+        let yItem={
+            ...basicYAxis,
+            IsAxis:item.IsAxis,
+            labels: {
+                formatter: function (ctx) {
+                    return sameSideIndex !== index ? '' : ctx.value;
+                },
+                align: 'center',
+                x: [0,2].includes(item.IsAxis) ? 5 : -5,
+                y:5,
+            },
+            tickWidth: sameSideIndex !== index ? 0 : 1,
+            title: {
+                text:  sameSideIndex !== index ? '' : `${item.Unit}`,
+                align: 'high',
+                rotation: 0,
+                y: -15,
+                x: (item.IsAxis===0 && temYRightTwoIndex>-1) ? -newData[temYRightTwoIndex].Unit.length*12 : 0,
+                textAlign: item.IsAxis===1 ? 'left' : 'right',
+                reserveSpace: false
+            },
+            opposite: [0,2].includes(item.IsAxis),
+            reversed: item.IsOrder,
+            min: item.MinData,
+            max: item.MaxData,
+            visible: sameSideIndex === index,
+            chartEdbInfo:item//指标数据用于在保存时读取指标数据
+        }
+        yAxis.push(yItem)
+        
+        if(item.DataList.length>0){
+            minAndMaxTimeTemArr.push(item.DataList[0].DataTimestamp)
+            minAndMaxTimeTemArr.push(item.DataList[item.DataList.length-1].DataTimestamp)
+        }
+        
+    })
+    
+    
+    // 设置x轴
+    // 找出所有指标的最大和最小时间 分成6段
+    let minTime=Math.min.apply(null,minAndMaxTimeTemArr)
+    let maxTime=Math.max.apply(null,minAndMaxTimeTemArr)
+
+
+    const bool_time = xTimeDiffer(minTime,maxTime)
+    if(bool_time){
+        xAxis={
+            ...basicXAxis,
+            labels: {
+              formatter: (ctx)=> {
+                return Highcharts.dateFormat('%m/%d', ctx.value)
+              }
+            }
+        }
+    }
+    // console.log(((maxTime-minTime)/6)/(24*3600*1000),':天');
+    // let maxYear=new Date(maxTime).getFullYear()
+    // let minYear=new Date(minTime).getFullYear()
+
+    xAxis={
+        ...basicXAxis,
+        tickInterval:((maxTime-minTime)/6)/(24*3600*1000)>30?(maxTime-minTime)/6:24*3600*1000*30,
+    }
+    
+
+    yAxis.forEach(item=>{
+        if(item.IsAxis===1){//左轴
+
+            // item.min=getAxisMaxOrMin(temYLeftArr,'min')
+            // item.max=getAxisMaxOrMin(temYLeftArr,'max')
+            item.min=data[temYLeftIndex].MinData
+            item.max=data[temYLeftIndex].MaxData
+        }else if (item.IsAxis===2){ // 右2轴
+            item.min=data[temYRightTwoIndex].MinData
+            item.max=data[temYRightTwoIndex].MaxData
+        }else{
+
+            // item.min=getAxisMaxOrMin(temYRightArr,'min')
+            // item.max=getAxisMaxOrMin(temYRightArr,'max')
+            item.min=data[temYRightIndex].MinData
+            item.max=data[temYRightIndex].MaxData
+        }
+    })
+
+    return {
+        series,
+        xAxis:[xAxis],
+        yAxis,
+        rangeSelector:{ enabled: false}
+    }
+}
+
+
+//季节图
+function setSeasonOpt(e){
+    let chartData={}
+    const data=e.EdbInfoList[0]
+
+    const colorsArr=['#4B0082','#7FFFAA','#FF4500','#808000','#EEE8AA','#849EC1','#8A4294','#578B5A','#FDA8C7','#53B3FF','#999999','#000000','#FFDF0C','#FF0000','#0033FF']
+    let series=[],yAxis=[]
+    //农历默认选中一年数据并隐藏按钮  公历显示全部数据
+    let rangeSelector={}
+
+    // 公历
+    if(calendarType.value==='公历'){
+        data.DataList.forEach((item,index)=>{
+             //预测指标配置
+            let predict_params =  data.EdbInfoCategoryType === 1 ? getSeasonPredictParams(item.CuttingDataTimestamp) : {};
+
+            let seriesItem={
+                data:[],
+                dataGrouping:{
+                    enabled:false
+                },
+                type:data.ChartStyle,
+                yAxis:index,
+                name:item.Year,
+                color:colorsArr.slice(-data.DataList.length)[index],                
+                visible:true,
+                ...predict_params
+            }
+            item.DataList=item.DataList||[]
+            for(let i of item.DataList){
+                seriesItem.data.push([i.DataTimestamp, i.Value])
+            }
+            series.push(seriesItem)
+
+            let yAxisItem={
+                IsAxis:data.IsAxis,
+                labels: {
+                    // formatter: function (ctx) {
+                    //     return ctx.value;
+                    // },
+                    align: 'center',
+                    y:5
+                },
+                title: {
+                    text:  `${data.Unit}`,
+                    align: 'high',
+                    rotation: 0,
+                    y: -15,
+                    offset: -(10 * data.Unit.length),
+                },
+                max: Number(data.MaxData),
+                min: Number(data.MinData),
+                lineWidth: 1,
+                lineColor: '#bfbfbf',
+                tickColor: '#bfbfbf',
+                offset: 0,
+                opposite: false,
+                reversed: false,
+                visible: true,
+                gridLineWidth: 0,
+                tickWidth: 1,
+                tickLength:5,
+                tickPosition: 'inside',
+                endOnTick: false,
+                startOnTick: false,
+                showLastLabel: true, //显示最后刻度值
+                tickPixelInterval: 50,
+                // chartEdbInfo:item//指标数据
+            }
+            yAxis.push(yAxisItem)
+        })
+
+        rangeSelector={ enabled: false}
+    }
+
+    // 农历
+    if(calendarType.value==='农历'){
+        let filterArr=data.DataList.List&&data.DataList.List.slice(1,data.DataList.List.length)||[]
+        console.log('aaa',filterArr);
+        filterArr.forEach((item,index)=>{
+             //预测指标配置
+            let predict_params =  data.EdbInfoCategoryType === 1 ? getSeasonPredictParams(item.CuttingDataTimestamp) : {};
+
+            let seriesItem={
+                data:[],
+                dataGrouping:{
+                    enabled:false
+                },
+                type:data.ChartStyle,
+                yAxis:index,
+                name:item.Year,
+                color:colorsArr.slice(-filterArr.length)[index],         
+                visible:true,
+                ...predict_params
+            }
+            let temarr=item.Items||[]
+            for(let i of temarr){
+                seriesItem.data.push([i.DataTimestamp, i.Value])
+            }
+            series.push(seriesItem)
+
+            let yAxisItem={
+                IsAxis:data.IsAxis,
+                labels: {
+                    formatter: function (ctx) {
+                        return ctx.value;
+                    },
+                    align: 'center',
+                    y:5
+                },
+                title: {
+                    text:  `${data.Unit}`,
+                    align: 'high',
+                    rotation: 0,
+                    y: -15,
+                    offset: -(10 * data.Unit.length),
+                },
+                max: Number(data.MaxData),
+                min: Number(data.MinData),
+                lineWidth: 1,
+                lineColor: '#bfbfbf',
+                tickColor: '#bfbfbf',
+                offset: 0,
+                opposite: false,
+                reversed: false,
+                visible: true,
+                gridLineWidth: 0,
+                tickWidth: 1,
+                tickLength:5,
+                tickPosition: 'inside',
+                endOnTick: false,
+                startOnTick: false,
+                showLastLabel: true, //显示最后刻度值
+                tickPixelInterval: 50,
+                // chartEdbInfo:item//指标数据用于在保存时读取指标数据
+            }
+            yAxis.push(yAxisItem)
+        })
+
+        rangeSelector={ 
+            enabled: true,
+            selected: 0,
+            inputStyle: {
+                display: 'none',
+            },
+            labelStyle: {
+                display: 'none',
+            },
+            buttonTheme: {
+                style: {
+                    display: 'none',
+                },
+            },
+            buttons: [
+                {
+                  type: 'month',
+                  count: 12,
+                  text: '12月',
+                },
+                {
+                  type: 'month',
+                  count: 15,
+                  text: '15月',
+                },
+                {
+                  type: 'all',
+                  text: '全部',
+                }
+            ]
+        }
+    }
+
+    chartData.chart={ spacing: [30, 8, 2, 8]}
+    chartData.series=series
+    chartData.yAxis=yAxis
+    chartData.rangeSelector=rangeSelector
+
+    // 季节图x轴显示月/日
+    let xAxis={
+        tickPosition: 'inside',
+		lineColor: '#bfbfbf',
+    	tickColor: '#bfbfbf',
+		tickLength:5,
+		type: 'datetime',
+		ordinal: false,
+		dateTimeLabelFormats: {
+			day: '%y/%m',
+			week: '%y/%m',
+			month: '%y/%m',
+			year: '%y/%m',
+		},
+        labels: {
+            formatter: (ctx)=> {
+                return Highcharts.dateFormat('%m/%d', ctx.value)
+            }
+        }
+    }
+
+    xAxis={
+        ...xAxis,
+        tickInterval:24*3600*1000*60,//季节图
+    }
+    
+
+    chartData.xAxis=[xAxis]
+
+    // 季节图提示框显示 月/日
+    chartData.tooltip={
+        split: false,
+        shared: true,
+        dateTimeLabelFormats: {
+          // 时间格式化字符
+          day: '%m/%d',
+          week: '%m/%d',
+          month: '%m/%d',
+          year: '%m/%d',
+        },
+        xDateFormat: '%m/%d',
+    }
+
+    return chartData
+}
+
+
+//堆叠图/组合图设置
+//本来和曲线图逻辑基本一致兼容下即可 为了以后便于维护和阅读还是拆开写吧
+function setStackOrCombinChart(e){
+    const data=e.EdbInfoList
+    //图表类型
+    const chartTypeMap = {
+        3: 'areaspline',
+        4: 'column',
+        6: ''
+    };
+    let chartStyle = chartTypeMap[e.ChartInfo.ChartType];
+
+    let series=[]
+    let yAxis=[]
+    let xAxis = {}
+
+    let temYLeftArr=[]
+    let temYRightArr=[]
+    let temYRightTwoArr = []
+    let temYLeftIndex,temYRightIndex,temYRightTwoIndex;
+
+    let minAndMaxTimeTemArr=[]//存放所有指标的最大最小时间
+
+    //有右二轴时排个序 按照左 右 右2的顺序
+    let newData = data.some(_ =>_.IsAxis===2) ? changeEdbOrder(data) : data;
+
+    newData.forEach((item,index)=>{
+
+        //轴位置值相同的下标
+        let sameSideIndex = newData.findIndex(i => i.IsAxis === item.IsAxis);
+
+        //堆叠图的yAxis必须一致 数据列所对应的y轴
+        let serie_yIndex = index;
+        if([3,4].includes(e.ChartInfo.ChartType)) {
+            // 类型为堆叠图时公用第一个指标y轴 
+            serie_yIndex =  0;
+        } else if(e.ChartInfo.ChartType ===6 && ['areaspline','column'].includes(item.ChartStyle)) {
+            // 组合图找第一个堆叠柱状或面积的作为公用
+            serie_yIndex = newData.findIndex(i => i.ChartStyle === item.ChartStyle);
+        }
+        //数据对应的y轴是公用轴则配置也共享
+        item.IsAxis = serie_yIndex === index ? item.IsAxis : newData[serie_yIndex].IsAxis;
+        item.IsOrder = serie_yIndex === index ? item.IsOrder : newData[serie_yIndex].IsOrder;
+
+        temYLeftIndex = [3,4].includes(e.ChartInfo.ChartType) 
+            ? (newData[serie_yIndex].IsAxis ? serie_yIndex : -1)
+            : data.findIndex((item) => item.IsAxis===1);
+        temYRightIndex = [3,4].includes(e.ChartInfo.ChartType) 
+            ? (newData[serie_yIndex].IsAxis ? -1 : serie_yIndex)
+            : data.findIndex((item) => !item.IsAxis);
+        temYRightTwoIndex = [3,4].includes(e.ChartInfo.ChartType) 
+            ? -1
+            : data.findIndex((item) => item.IsAxis===2);
+
+        let dynamic_title = item.EdbName;
+        let dynamic_arr = newData.filter(
+          (item) => dynamic_title === item.EdbName
+        );
+        //处理数据列name
+        let temName= setDyncmicSerieName(item,dynamic_arr)  
+
+        //预测指标配置
+        let predict_params = item.EdbInfoCategoryType === 1 ? getPredictParams(item) : {};
+
+        let seriesItemObj={
+            data:[],
+            dataGrouping:{
+                enabled:false
+            },
+            type: chartStyle || item.ChartStyle,
+            yAxis:serie_yIndex,
+            name:temName,
+            color: item.ChartColor,
+            lineWidth: (e.ChartInfo.ChartType === 6 && item.ChartStyle === 'spline') ? Number(item.ChartWidth) : 0,
+            fillColor: (e.ChartInfo.ChartType === 3 || (e.ChartInfo.ChartType === 6 && item.ChartStyle === 'areaspline')) ? item.ChartColor : undefined,
+            visible:true,
+            LatestDate:item.LatestDate,
+            LatestValue:item.LatestValue,
+            ...predict_params
+        }
+        item.DataList = item.DataList || [];
+        for (let i of item.DataList) {
+          seriesItemObj.data.push([i.DataTimestamp, i.Value]);
+        }
+        series.push(seriesItemObj)
+        
+
+        // 设置y轴
+        if(item.IsAxis){
+            temYLeftArr.push(item)
+        }else{
+            temYRightArr.push(item)
+        }
+
+        let yItem={
+            ...basicYAxis,
+            IsAxis:item.IsAxis,
+            labels: {
+                formatter: function (ctx) {
+                    return sameSideIndex !== index ? '' : ctx.value;
+                },
+                align: 'center',
+                x: [0,2].includes(item.IsAxis) ? 5 : -5,
+                y:5,
+            },
+            title: {
+                text:  sameSideIndex !== index ? '' : `${item.Unit}`,
+                align: 'high',
+                rotation: 0,
+                y: -15,
+                x: (item.IsAxis===0 && temYRightTwoIndex>-1) ? -newData[temYRightTwoIndex].Unit.length*12 : 0,
+                textAlign: item.IsAxis===1 ? 'left' : 'right',
+                reserveSpace: false
+            },
+            opposite: [0,2].includes(item.IsAxis),
+            reversed: item.IsOrder,
+            min: item.MinData,
+            max: item.MaxData,
+            tickWidth: sameSideIndex !== index ? 0 : 1,
+            visible: serie_yIndex === index && sameSideIndex ===index,
+            chartEdbInfo:item//指标数据用于在保存时读取指标数据
+        }
+        yAxis.push(yItem)
+        
+        if(item.DataList.length>0){
+            minAndMaxTimeTemArr.push(item.DataList[0].DataTimestamp)
+            minAndMaxTimeTemArr.push(item.DataList[item.DataList.length-1].DataTimestamp)
+        }
+        
+    })
+    
+    
+    // 设置x轴
+    // 找出所有指标的最大和最小时间 分成6段
+    let minTime=Math.min.apply(null,minAndMaxTimeTemArr)
+    let maxTime=Math.max.apply(null,minAndMaxTimeTemArr)
+
+
+    const bool_time = xTimeDiffer(minTime,maxTime)
+    if(bool_time){
+        xAxis={
+            ...basicXAxis,
+            labels: {
+              formatter: (ctx)=> {
+                return Highcharts.dateFormat('%m/%d', ctx.value)
+              }
+            }
+        }
+    }
+
+    xAxis={
+        ...basicXAxis,
+        tickInterval:((maxTime-minTime)/6)/(24*3600*1000)>30?(maxTime-minTime)/6:24*3600*1000*30,
+    }
+    
+
+    yAxis.forEach(item=>{
+        if(item.IsAxis === 1){//左轴
+            item.min=data[temYLeftIndex].MinData
+            item.max=data[temYLeftIndex].MaxData
+        }else if (item.IsAxis===2){ // 右2轴
+            item.min=data[temYRightTwoIndex].MinData
+            item.max=data[temYRightTwoIndex].MaxData
+        }else{
+            item.min=data[temYRightIndex].MinData
+            item.max=data[temYRightIndex].MaxData
+        }
+    })
+
+    return {
+        series,
+        xAxis:[xAxis],
+        yAxis,
+        rangeSelector:{ enabled: false}
+    }
+}
+
+
+/* 散点图 第一个指标值为x轴 第二个指标为y轴*/
+function setScatterOptions(data){
+    const dataList=data.EdbInfoList
+    const { ChartInfo } = data;
+
+    // 取2个指标中日期相同的数据
+    const real_data = [];
+    let tmpData_date = {};//用来取点对应的日期
+    let data1 =  _.cloneDeep(dataList)[0].DataList || [];
+    let data2 =  _.cloneDeep(dataList)[1].DataList || [];
+    data1.forEach((_item) => {
+        data2.forEach((_item2) => {
+            if(_item.DataTimestamp === _item2.DataTimestamp) {
+                //日期
+                let itemIndex =_item.Value + "_" +_item2.Value
+                if(tmpData_date[itemIndex]) {
+                    tmpData_date[itemIndex].push( moment(_item.DataTimestamp).format('YYYY/MM/DD'))
+                } else {
+                    tmpData_date[itemIndex] = [moment(_item.DataTimestamp).format('YYYY/MM/DD')]
+                }
+            
+                //值
+                real_data.push({
+                    x: _item.Value,
+                    y: _item2.Value
+                })
+            }
+        })
+    })
+    real_data.sort((x,y) => x-y);
+
+    //悬浮窗 拼接日期 原始指标名称
+    let tooltip = {
+        formatter: function() {
+            return `<strong>${ tmpData_date[this.x+'_'+this.y].length > 4 ? tmpData_date[this.x+'_'+this.y].slice(0,4).join()+'...' : tmpData_date[this.x+'_'+this.y].join() }</strong><br>
+            ${dataList[0].EdbName}: <span style="font-weight: 600">	${this.x}</span><br>
+            ${dataList[1].EdbName}: <span style="font-weight: 600">	${this.y}</span>
+            `
+        }
+    }
+
+    const { IsOrder,ChartColor } = dataList[0];
+    //y轴
+    let yAxis = {
+        title: {
+            text:  `${dataList[1].Unit}`,
+            align: 'high',
+            rotation: 0,
+            y: -15,
+            offset: -(10 * dataList[1].Unit.length),
+        },
+        labels: {
+            formatter: function (ctx) {
+            return ctx.value;
+            },
+            align: 'center',
+        },
+        opposite: false,
+        reversed: IsOrder,
+        min: Number(dataList[0].MinData),
+        max: Number(dataList[0].MaxData),
+        tickWidth: 1,
+        tickLength: 5,
+        lineWidth: 1,
+        lineColor: '#bfbfbf',
+        tickColor: '#bfbfbf',
+        offset: 0,
+        visible: true,
+        gridLineWidth: 0,
+        tickPosition: 'inside',
+        endOnTick: false,
+        startOnTick: false,
+        showLastLabel: true,
+        tickPixelInterval: 50
+    }
+
+    //数据列
+    let series = {
+        data: [],
+        type: 'scatter',
+        name: `${ChartInfo.ChartName}${IsOrder ? '(逆序)' : ''}`,
+        color: ChartColor,
+        visible:true,
+        lineWidth: 0
+    }
+    real_data.forEach(_ => {
+        series.data.push([_.x,_.y])
+    })
+    
+    return {
+        title: {
+            text:''
+        },
+        series: [ series ],
+        yAxis,
+        xAxis: {
+            ...scatterXAxis,
+            title: {
+                text:  `${dataList[0].Unit}`,
+                align: 'high',
+                rotation: 0,
+                x: 0,
+                offset: 20,
+            },
+        },
+        tooltip
+    }
+}
+
+
+/* 奇怪柱形图 */
+const barDateList = ref([]);//柱形图的绘图数据
+const barXData = ref([]);//柱形图的x轴
+const barEdbData = ref([]);//柱形图的表格数据 只用于取值
+let axisLimitData={}
+/* 奇怪柱状图 和其他逻辑无公用点 依赖数据为单独的数据
+    x轴为指标名称的柱形图 以日期作为series
+*/
+function setBarChart (){
+    let seriesData = [];
+    const data = _.cloneDeep(barDateList.value);
+
+    //x轴
+    let xAxis = {
+        ...scatterXAxis,
+        categories: barXData.value,
+        tickWidth: 1,
+        title: {
+            text:  ``,
+            align: 'high',
+            rotation: 0,
+            x: 0,
+            offset: 20,
+        },
+    }
+
+    const { leftMin,leftMax } = axisLimitData;
+    console.log(leftMin,leftMax)
+    //y轴
+    let yAxis = {
+        ...basicYAxis,
+        title: {
+            text:  ``,
+            align: 'high',
+            rotation: 0,
+            y: -15,
+            offset: 0,
+        },
+        labels: {
+            formatter: function (ctx) {
+            let val = ctx.value;
+            return val;
+            },
+            align: 'center',
+        },
+        min: Number(leftMin),
+        max: Number(leftMax),
+        opposite: false,
+        tickWidth: 1,
+    }
+
+    //数据列
+    data.forEach(item => {
+        let serie_item = {
+            data: item.Value,
+            type: 'column',
+            yAxis: 0,
+            name: item.Name || item.Date,
+            color: item.Color,
+            chartType: 'linear',              
+            visible:true,
+        };
+        seriesData.push(serie_item)
+    })
+    
+    return {
+        title: {
+            text:''
+        },
+        plotOptions: {
+            column:{
+            stacking: null,
+            },
+        },
+        series: seriesData,
+        yAxis: [ yAxis ],
+        xAxis
+    }
+}
+/* 获取图表详情后赋值柱状图数据 */
+function initBarData(data){
+    const { XEdbIdValue,YDataList,EdbInfoList,ChartInfo } = data;
+
+    let xData = XEdbIdValue.map(_ => EdbInfoList.find(edb => edb.EdbInfoId===_).EdbAliasName)
+    console.log(xData)
+
+    barDateList.value = YDataList;
+    barXData.value = xData;
+    barEdbData.value = EdbInfoList;
+
+    axisLimitData.leftMin=Number(ChartInfo.LeftMin)
+    axisLimitData.leftMax=Number(ChartInfo.LeftMax)
+
+    setBarChart();
+}
+
+
+/* 商品价格曲线设置 绘图逻辑同奇怪柱形图*/
+const commodityChartData = ref([]);//商品价格图的绘图数据
+const commodityXData = ref([]);//商品价格图的x轴
+const commodityEdbList = ref([]);//商品价格图的表格数据 只用于取值
+/* 商品价格曲线设置 绘图逻辑同奇怪柱形图*/
+const setCommodityChart = (leftMin,leftMax) => {
+    
+    let seriesData = [];
+    const data = _.cloneDeep(commodityChartData.value);
+
+    //x轴
+    let xAxis = {
+        ...scatterXAxis,
+        categories: commodityXData.value.map(_ => _.Name),
+        tickWidth: 1,
+        title: {
+            text:  ``,
+            align: 'high',
+            rotation: 0,
+            x: 0,
+            offset: 20,
+        },
+    }
+
+    //y轴
+    let yAxis = {
+        ...basicYAxis,
+        title: {
+            text: commodityEdbList.value[0].Unit,
+            align: 'high',
+            rotation: 0,
+            y: -15,
+            offset: 0,
+        },
+        labels: {
+            formatter: function (ctx) {
+            let val = ctx.value;
+            return val;
+            },
+            align: 'center',
+        },
+        min: Number(leftMin),
+        max: Number(leftMax),
+        opposite: false,
+        tickWidth: 1,
+    }
+
+    //数据列
+    data.forEach(item => {
+        //处理首或/尾全是无效数据的以null填充
+        let filterData = filterInvalidData(item)
+
+        let serie_item = {
+            data: filterData,
+            type: 'spline',
+            yAxis: 0,
+            name: item.Name,
+            color: item.Color,
+            chartType: 'linear',
+            lineWidth: 3,
+            visible: true,
+            marker: {
+                enabled: false
+            }
+        };
+        seriesData.push(serie_item)
+    })
+
+    //tooltip
+    let tooltip = {
+        formatter: function() {
+            const ctx = this;
+            let str = '';
+            ctx.points.forEach(item => {
+              let obj_item = data.find(_ => _.Name === item.series.name);
+              let index = commodityXData.value.findIndex(_ => _.Name === ctx.x);
+
+              str+=`<b>${ commodityEdbList.value.find(_ => _.EdbInfoId === obj_item.XEdbInfoIdList[index]).EdbName }</b>`
+
+              if(!obj_item.NoDataEdbList.includes(obj_item.XEdbInfoIdList[index])) {
+                str += `<br><span style="color:${item.color}">\u25CF</span>${obj_item.Date}: ${item.y}<br>`
+              }else {
+                str += `<br><span style="color:${item.color}">\u25CF</span>${obj_item.Date}: 无<br>`
+              }
+            })
+            return str
+        },
+        shared: true
+    }
+    
+    return {
+        title: {
+            text:''
+        },
+        series: seriesData,
+        yAxis: [ yAxis ],
+        xAxis,
+        tooltip
+    }
+};
+/* 处理无效数据为null */
+function filterInvalidData(item){
+    let validateArr = item.XEdbInfoIdList.filter(_ =>!item.NoDataEdbList.includes(_));
+
+    let first_index = item.XEdbInfoIdList.findIndex(_ => _ === validateArr[0]);
+    let last_index = item.XEdbInfoIdList.findIndex(_ => _ === validateArr[validateArr.length-1]);
+    console.log('first_index',first_index)
+    console.log('last_index',last_index)
+
+    let arr = item.Value.map((item,index) => {
+    if(index < first_index || index > last_index) {
+        return null
+    }else {
+        return item
+    }
+    })
+
+    return arr;
+}
+function initCommodityData(data){
+    const { XDataList,YDataList,EdbInfoList,ChartInfo } = data;
+
+    commodityEdbList.value = EdbInfoList;
+    commodityChartData.value = YDataList;
+    commodityXData.value = XDataList;
+
+    setCommodityChart(ChartInfo.LeftMin,ChartInfo.LeftMax);
+}
+
+
+//相关性图表
+function initRelevanceChart(data){
+    // 处理X轴
+    let xAxis={
+        categories: data.XEdbIdValue,
+        tickWidth: 1,
+        title: {
+          text:  `期数(${data.CorrelationChartInfo.LeadUnit})`,
+          align: 'high',
+          rotation: 0,
+          x: 0,
+          y:10,
+          offset: 20,
+        },
+        tickInterval: 1,
+        offset:0,
+        tickmarkPlacement:'on'
+    }
+
+    // 处理Y轴
+    let yAxis={
+        ...basicYAxis,
+        title: {
+          text: '相关性系数',
+          textCh: '相关性系数',
+          textEn: 'Correlation coefficient',
+          align: 'high',
+          rotation: 0,
+          y: -15,
+          offset: 0,
+        },
+        labels: {
+          formatter: function (ctx) {
+            let val = ctx.value;
+            return val;
+          },
+          align: 'center',
+        },
+        min: -1,
+        max: 1,
+        opposite: false,
+        tickWidth: 1,
+        tickInterval:0.2,
+    }
+
+    //处理series
+    let seriesData=[]
+    data.YDataList.forEach(item=>{
+        let serie_item = {
+          data: item.Value,
+          type: 'spline',
+          yAxis: 0,
+          name: data.ChartInfo.ChartName,
+          nameCh: data.ChartInfo.ChartName,
+          nameEn: data.ChartInfo.ChartName,
+          color: '#00f',
+          chartType: 'linear',
+          lineWidth: 3,
+          visible:true,
+          marker: {
+            enabled: false
+          }
+        };
+        seriesData.push(serie_item)
+    })
+
+    let tooltip = {
+        formatter: function() {
+          let str = `<p>相关性系数:${this.y.toFixed(4)}</p><br><p>领先${this.x}期</p>`
+          return str
+        },
+        formatterCh: function() {
+          let str = `<p>相关性系数:${this.y.toFixed(4)}</p><br><p>领先${this.x}期</p>`
+          return str
+        },
+        formatterEn: function() {
+          let str = `<p>Correlation coefficient:${this.y.toFixed(4)}</p><br><p>lead${this.x}stage</p>`
+          return str
+        }
+    }
+    
+    // nextTick(()=>{
+    //     const hEl=document.getElementById('chart'+data.ChartInfo.UniqueCode)
+    //     console.log(hEl.offsetHeight);
+    //     xAxis.offset=-(hEl.offsetHeight-74)/2
+    //     chartData.value = {
+    //         isRelevanceChart:true,
+    //         title: {
+    //             text:''
+    //         },
+    //         series: seriesData,
+    //         yAxis: [yAxis] ,
+    //         xAxis:xAxis,
+    //         tooltip
+    //     }
+    // })
+
+    return {
+        isRelevanceChart:true,
+        title: {
+            text:''
+        },
+        series: seriesData,
+        yAxis: [yAxis] ,
+        xAxis:xAxis,
+        tooltip
+    }
+}

+ 1 - 0
src/main.js

@@ -3,6 +3,7 @@ import App from './App.vue'
 import router from "./router";
 import './plugin/vant'
 import 'normalize.css'
+import './assets/styles/common.scss'
 // import './style.css'
 
 

+ 3 - 0
src/router/index.js

@@ -1,4 +1,5 @@
 import { createRouter, createWebHistory } from "vue-router";
+import {pptRoutes} from './ppt'
 /**
  * 说明
  * 此文件为路由配置入口文件
@@ -37,6 +38,8 @@ const routes = [
       }
     ]
   },
+  // ppt模块
+  ...pptRoutes,
   {
     path: "/:pathMatch(.*)",
     name: "error",

+ 15 - 1
src/router/ppt.js

@@ -1 +1,15 @@
-// ppt路由模块
+// ppt路由模块
+export const pptRoutes=[
+    {
+        path:"/ppt/index",
+        name:"PPTIndex",
+        component: () => import("@/views/ppt/Index.vue"),
+        meta: { title: "智能PPT" },
+    },
+    {
+        path:"/ppt/detail",
+        name:"PPTDetail",
+        component: () => import("@/views/ppt/Detail.vue"),
+        meta: { title: "智能PPT" },
+    }
+]

+ 1 - 1
src/views/Login.vue

@@ -16,7 +16,7 @@ const onSubmit = (values) => {
     const params={
         Username:values.username,
         Password:md5(values.password),
-        IsRemember:false
+        IsRemember:true
     }
     apiLogin(params).then(res=>{
         if(res.Ret===200){

+ 35 - 0
src/views/ppt/Detail.vue

@@ -0,0 +1,35 @@
+<script setup>
+import {computed, ref} from 'vue'
+import { useRoute } from "vue-router";
+import {apiPPTDetail} from '@/api/ppt'
+import {createPPTContent,getTemplate} from './hooks/createPPTContent'
+
+const route=useRoute()
+
+const pptId=route.query.id
+// 获取ppt详情
+let conArr=ref([])
+async function getPPTDetail(){
+    const res=await apiPPTDetail({PptId:Number(pptId)})
+    conArr.value=createPPTContent(res.Data)
+}
+getPPTDetail()
+
+</script>
+
+<template>
+    <div class="ppt-detail-page">
+        <template v-for="(item,index) in conArr" :key="item.id">
+            <component 
+                :is="getTemplate(item.modelId)" 
+                :pageData="{...item,pptPageIndex:index}"
+            />
+        </template>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.ppt-detail-page{
+    overflow-x: auto;
+}
+</style>

+ 65 - 0
src/views/ppt/Index.vue

@@ -0,0 +1,65 @@
+<script setup>
+import {ref,reactive} from 'vue'
+import {apiPPTClassify} from '@/api/ppt'
+
+const searchVal=ref('')
+
+// 获取ppt分类数据
+const classifyState = reactive({
+    privateList:[],
+    publicList:[],
+    activeType:['1','2'],//一级分类是否展开
+    myActiveType:[],//我的ppt文件夹是否展开
+    pubActiveType:[],//公共ppt文件夹是否展开
+})
+async function getPPTClassify(){
+    const res=await apiPPTClassify()
+    if(res.Ret===200){
+        classifyState.privateList=res.Data.PrivateList||[]
+        classifyState.publicList=res.Data.PublicList||[]
+    }
+}
+getPPTClassify()
+
+
+</script>
+
+<template>
+    <div class="ppt-index-page">
+        <div class="search-box">
+            <van-search v-model="searchVal" placeholder="请输入搜索关键词" />
+        </div>
+        <div class="classify-list-wrap">
+            <van-collapse v-model="classifyState.activeType">
+                <van-collapse-item title="我的PPT" name="1">
+                    <van-collapse v-model="classifyState.myActiveType">
+                        <van-collapse-item 
+                            :title="item.GroupName" 
+                            :name="item.GroupId" 
+                            v-for="item in classifyState.privateList"
+                            :key="item.GroupId"
+                        >   
+                            <div v-for="_item in item.PptList" :key="_item.GroupId">{{_item.Title}}</div>
+                        </van-collapse-item>
+                    </van-collapse>
+                </van-collapse-item>
+                <van-collapse-item title="公共PPT" name="2">
+                    <van-collapse v-model="classifyState.pubActiveType">
+                        <van-collapse-item 
+                            :title="item.GroupName" 
+                            :name="item.GroupId" 
+                            v-for="item in classifyState.publicList"
+                            :key="item.GroupId"
+                        >   
+                            <div v-for="_item in item.PptList" :key="_item.GroupId">{{_item.Title}}</div>
+                        </van-collapse-item>
+                    </van-collapse>
+                </van-collapse-item>
+            </van-collapse>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+
+</style>

+ 27 - 0
src/views/ppt/components/ChartWrap.vue

@@ -0,0 +1,27 @@
+<script setup>
+import ChartEl from '@/components/chart/Index.vue'
+
+const props=defineProps({
+    itemData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+    <div class="ppt-chart-box">
+        <ChartEl 
+            :code="itemData.chartId" 
+            :renderId="'chart'+itemData.chartId+'_'+itemData.pptPageIndex+'_'+itemData.position"
+        />
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.ppt-chart-box{
+    width: 100%;
+    height: 100%;
+}
+</style>

+ 16 - 0
src/views/ppt/components/RichText.vue

@@ -0,0 +1,16 @@
+<script setup>
+
+const props=defineProps({
+    itemData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+    <div class="rich-text-box">
+        <div v-html="itemData.richContent"></div>
+    </div>
+</template>

+ 107 - 0
src/views/ppt/hooks/createPPTContent.js

@@ -0,0 +1,107 @@
+
+import {formatPPTDate} from '../utils/index'
+import {bgList} from '../utils/config'
+import {ref,markRaw} from 'vue'
+import Cover from '../template/Cover.vue'
+import FormatOne from '../template/FormatOne.vue'
+import FormatTwo from '../template/FormatTwo.vue'
+import FormatThree from '../template/FormatThree.vue'
+import ChartWrap from '../components/ChartWrap.vue'
+import RichText from '../components/RichText.vue'
+
+/**
+ * 格式化ppt内容
+ * @param params ppt详情接口返回的数据
+ * modelId 模板1-9 0代表封面 -1代表尾页
+ */
+export function createPPTContent(params){
+    const {
+        Content,
+        Title,
+        ReportType,
+        BackgroundImg,
+        PptDate,
+        TemplateType,
+        ReportId,
+        ModifyTime,
+        PublishTime
+    }=params
+
+    let arr=[]//ppt内容数组
+
+    // 封面页数据
+    const coverPageData={
+        Title,
+		ReportType,
+		BackgroundImg,
+		PptDate:formatPPTDate('zh',PptDate),
+		TemplateType,
+        BackIndex:TemplateType-1,
+        imgLocalUrl:bgList[TemplateType-1],
+        modelId:0,
+        id:0,
+    }
+    arr.push(coverPageData)
+
+    const conArr=JSON.parse(Content)
+    arr=[...arr,...conArr,{name:'back',modelId:-1,id:-1}]
+    return arr
+}
+
+/**
+ * ppt模板Map
+ * 根据modelId获取是哪个模板组件
+ * 此处动态组件需要用markRaw
+ */
+export function getTemplate(modelId){
+    const modelMap=new Map([
+        [0,markRaw(Cover)],
+        [1,markRaw(FormatOne)],
+        [2,markRaw(FormatTwo)],
+        [3,markRaw(FormatThree)],
+        [4,'FormatFour'],
+        [5,'FormatFive'],
+        [6,'FormatSix'],
+        [7,'FormatSeven'],
+        [8,'FormatEight'],
+        [9,'FormatNine'],
+        [-1,'Footer'],
+    ])
+    return modelMap.get(modelId)
+}
+
+/**
+ * 获取ppt页面内部是哪种组件
+ * @param position 该模板中哪个位置 
+ * @param list 该ppt页的element数据
+ */
+export function getPPTContentType(position,list){
+    const typeMap=new Map([
+        ['chart',markRaw(ChartWrap)],
+        ['text',markRaw(RichText)]
+    ])
+    // 查找出list中的position和position对应的类型
+    let type=''
+    list.forEach(item => {
+        if(item.position===position){
+            type=item.type
+        }
+    });
+    return typeMap.get(type)
+}
+
+/**
+ * 获取ppt页面中某模块的数据
+ * @param position 该模板中哪个位置 
+ * @param data 该ppt页的数据
+ */
+export function getPPTContentItemData(position,data){
+    let obj={}
+    data.elements.forEach(item => {
+        if(item.position===position){
+            obj=item
+        }
+    });
+    return {...obj,pptPageIndex:data.pptPageIndex}
+}
+

+ 31 - 0
src/views/ppt/style/common.scss

@@ -0,0 +1,31 @@
+$pptItemW:1000px;
+$pptItemH:700px;
+
+.ppt-item-box{
+    width: $pptItemW;
+    height: $pptItemH;
+    background: url('https://hzstatic.hzinsights.com/static/ppt_m/pptitem_bg.png');
+    background-size: cover;
+    background-repeat: no-repeat;
+    margin-top: 10px;
+    position: relative;
+    .ppt-title-box{
+        width: 68%;
+        height: 7%;
+        position: absolute;
+        top: 5.5%;
+        left: 10%;
+        z-index: 100;
+        display: flex;
+        font-size: 24px;
+        color: #333;
+        align-items: center;
+        transform-origin: 0 0;
+    }
+    .ppt-content-box{
+        position: relative;
+        top: 14%;
+        width: 100%;
+        height: 86%;
+    }
+}

+ 40 - 0
src/views/ppt/template/Cover.vue

@@ -0,0 +1,40 @@
+<script setup>
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+</script>
+
+<template>
+    <div class="cover">
+        <img :src="pageData.imgLocalUrl.image_url" class="pptbg"  style="width:100%"/>
+        <div style="width:62%; font-size:16px; text-align:center; line-height:1.6; color:#fff; position:absolute; right:20px; top:50%;zIndex:20;">
+            <p style="height:5px; border-top:1px solid #fff;marginBottom:21px;"></p>
+            <p style="font-size:28px;">{{ pageData.Title }}</p>
+            <p style="display:flex; align-items:center; justify-content:center;margin:10px 0;">
+                <span style="display:inline-block; width:15px; margin-right:5px; border-top:1px solid #fff;"></span>
+                <span>弘则弥道(上海)投资咨询有限公司</span>
+                <span
+                style="display:inline-block; width:14px; height:14px; background:#fff; border-radius:100%; margin:0 5px;"></span>
+                <span>{{ pageData.ReportType }}</span>
+                <span style="display:inline-block; width:15px; margin-left:5px; border-top:1px solid #fff;"></span>
+            </p>
+            <p>FICC研究部</p>
+            <p>{{pageData.PptDate}}</p>
+            <p style="width:80%; height:1px; border-bottom:1px solid #fff; margin:21px auto 0;"></p>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.cover{
+    width: 1000px;
+    height: 700px;
+    position: relative;
+    overflow: hidden;
+}
+</style>
+

+ 26 - 0
src/views/ppt/template/FormatEight.vue

@@ -0,0 +1,26 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+</style>

+ 26 - 0
src/views/ppt/template/FormatFive.vue

@@ -0,0 +1,26 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+</style>

+ 26 - 0
src/views/ppt/template/FormatFour.vue

@@ -0,0 +1,26 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+</style>

+ 26 - 0
src/views/ppt/template/FormatNine.vue

@@ -0,0 +1,26 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+</style>

+ 31 - 0
src/views/ppt/template/FormatOne.vue

@@ -0,0 +1,31 @@
+<script setup>
+
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+            <component 
+				:is="getPPTContentType(1,pageData.elements)"
+				:itemData="getPPTContentItemData(1,pageData)"
+			></component>
+        </div>
+	</div>
+</template>
+
+
+
+<style lang="scss" scoped>
+@import '../style/common.scss';
+
+</style>

+ 26 - 0
src/views/ppt/template/FormatSeven.vue

@@ -0,0 +1,26 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+</style>

+ 26 - 0
src/views/ppt/template/FormatSix.vue

@@ -0,0 +1,26 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+</style>

+ 58 - 0
src/views/ppt/template/FormatThree.vue

@@ -0,0 +1,58 @@
+<script setup>
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			<div class="left-box">
+				<div class="left-half-box">
+					<component 
+						:is="getPPTContentType(1,pageData.elements)"
+						:itemData="getPPTContentItemData(1,pageData)"
+					/>
+				</div>
+				<div class="left-half-box">
+					<component 
+						:is="getPPTContentType(2,pageData.elements)"
+						:itemData="getPPTContentItemData(2,pageData)"
+					/>
+				</div>
+			</div>
+			<div class="right-box">
+				<component 
+					:is="getPPTContentType(3,pageData.elements)"
+					:itemData="getPPTContentItemData(3,pageData)"
+				/>
+			</div>
+		</div>
+	</div>
+</template>
+
+
+
+<style scoped lang="scss">
+@import '../style/common.scss';
+.ppt-content-box{
+	display: flex;
+	.left-box{
+		width: 50%;
+		.left-half-box{
+			width: 100%;
+			height: 50%;
+		}
+	}
+	.right-box{
+		width: 50%;
+	}
+}
+</style>

+ 49 - 0
src/views/ppt/template/FormatTwo.vue

@@ -0,0 +1,49 @@
+<script setup>
+
+import {getPPTContentType,getPPTContentItemData} from '../hooks/createPPTContent'
+
+const props=defineProps({
+    pageData:{
+        type:Object,
+        default:{}
+    }
+})
+
+</script>
+
+<template>
+	<div class="ppt-item-box">
+		<div class="ppt-title-box">{{pageData.title}}</div>
+		<div class="ppt-content-box">
+			<div class="left-box">
+				<component 
+					:is="getPPTContentType(1,pageData.elements)"
+					:itemData="getPPTContentItemData(1,pageData)"
+				/>
+			</div>
+			<div class="right-box">
+				<component 
+					:is="getPPTContentType(2,pageData.elements)"
+					:itemData="getPPTContentItemData(2,pageData)"
+				/>
+			</div>
+		</div>
+	</div>
+</template>
+
+<style lang="scss" scoped>
+@import '../style/common.scss';
+.ppt-content-box{
+	display: flex;
+	.left-box{
+		width: 60%;
+		height: 100%;
+		flex-shrink: 0;
+	}
+	.right-box{
+		flex: 1;
+		flex-shrink: 0;
+		height: 100%;
+	}
+}
+</style>

+ 8 - 0
src/views/ppt/utils/config.js

@@ -0,0 +1,8 @@
+// ppt 公共配置项
+
+//ppt封面背景图
+export const bgList=[
+    {image_url:'https://hzstatic.hzinsights.com/static/ppt_m/pptcover_bg3.jpg'},
+    {image_url:'https://hzstatic.hzinsights.com/static/ppt_m/pptcover_bg4.jpg'},
+    {image_url:'https://hzstatic.hzinsights.com/static/ppt_m/pptcover_bg5.jpg'},
+]

+ 7 - 0
src/views/ppt/utils/index.js

@@ -0,0 +1,7 @@
+//转换pptDate的格式,中文:yyyy年MM月;英文:yyyy.MM
+export function formatPPTDate(lang, date){
+    if (lang !== "en") return date;
+    let str = date.replace("年", ".");
+    str = str.replace("月", "");
+    return str;
+};