Browse Source

fix:插入AI预测模型bug修改

chenlei 2 months ago
parent
commit
fc2fb261b8

+ 20 - 0
src/hooks/chart/config.js

@@ -243,3 +243,23 @@ export const yearSelectOpt = [
 // 拥有相同配置的图表类型集合
 export const sameOptionType=[1,3,4,5,6]
 
+/* 图表年份筛选框 */
+export const yearSelector = [
+	{
+		name: '15年至最新值',//'15年至最新值'
+		value: 3,
+	},
+	{
+		name: '20年至最新值',//'20年至最新值'
+		value: 9,
+	},
+	{
+		name: '23年至最新值',//'23年至最新值'
+		value: 12,
+	},
+	{
+		name: '24年至最新值',//'24年至最新值'
+		value: 13,
+	},
+]
+

+ 233 - 1
src/hooks/chart/render.js

@@ -1,7 +1,7 @@
 // 图渲染逻辑模块
 
 import {onMounted,ref,nextTick,reactive} from 'vue'
-import {chartDefaultOpts,scatterXAxis,basicYAxis,basicXAxis,leadUnitEnMap,relevanceUnitEnMap,seasonOptions} from './config'
+import {chartDefaultOpts,scatterXAxis,basicYAxis,basicXAxis,leadUnitEnMap,relevanceUnitEnMap,seasonOptions, yearSelector} from './config'
 import Highcharts from 'highcharts/highstock';
 import HighchartsFormat from 'highcharts';
 import HighchartsMore from 'highcharts/highcharts-more';
@@ -51,6 +51,9 @@ const axisLimitState = reactive({//极值数据
 let useSelfLimit = false
 let isUseSelfLimit = ref(false)
 
+const year_select = yearSelector[0].value
+const select_date = '' //自定义时间段
+
 const screen = ref(document.body.clientWidth < 1200 ? 'phone' : 'pc');
 // webcomponent的EtaChart.ce.vue文件用到了这里的逻辑
 // 有修改了 chartRender,setLimitData 这俩方法的,需要重新编辑下webcomponent(npm run build.lib),然后替换掉report项目的对应文件eta_comp.js
@@ -108,6 +111,8 @@ export function chartRender({data,renderId,lang='zh',changeLangIsCheck,showChart
     chartData.value=data
    /*  useSelfLimit = ['/myETA/chartdetail','/chartETA/chartdetail'].includes(window.location.pathname) */
    useSelfLimit = shouldUseSelfLimit
+   console.log(data.ChartInfo.Source);
+   
     if([1,11,12].includes(data.ChartInfo.Source)){
         const chartSetMap = {
             1: setSplineOpt,
@@ -142,6 +147,12 @@ export function chartRender({data,renderId,lang='zh',changeLangIsCheck,showChart
         axisLimitState.xMax=data.ChartInfo.XMax?Number(data.ChartInfo.XMax):Number(data.DataResp.XMaxValue),
 
         chartOpt = setCrossVarietyChart(data)
+    }else if (data.ChartInfo.Source === 14 || data.ChartInfo.Source === 15) {
+        if (data.ChartInfo.Source === 14) {
+            chartOpt = setAIPredictChartDaily(data);
+        } else {
+            chartOpt = setAIPredictChart(data);
+        }
     }
 
     /* 
@@ -2715,7 +2726,228 @@ function setRadarChart({DataResp,EdbInfoList,ChartInfo}) {
     }
 }
 
+// AI预测模型日度预测图表
+function setAIPredictChartDaily({ DataResp, EdbInfoList, ChartInfo }) {
+    axisLimitState.leftIndex = -1;
+    axisLimitState.rightIndex = -1;
+    axisLimitState.rightTwoIndex = -1;
+
+    const chart = {
+        ...defaultOpts.chart,
+        plotBorderColor: '#000',
+        plotBorderWidth: 1
+    };
+
+    const plotLines = [
+        {
+            color: '#000',
+            width: 1,
+            dashStyle: 'Dash',
+            label: {
+                text: 'Future Split',
+                verticalAlign: 'middle',
+                textAlign: 'center'
+            },
+            value: DataResp ? DataResp.ActualLatestTimestamp : 0
+        }
+    ];
 
+    const xAxis = {
+        tickPosition: 'inside',
+        lineColor: '#bfbfbf',
+        labels: {
+            formatter: function (ctx) {
+                return Highcharts.dateFormat('%Y-%m', ctx.value);
+            },
+            style: {}
+        },
+        lineColor: '#000',
+        tickWidth: 0,
+        tickColor: '#ccc',
+        gridLineColor: '#ccc',
+        gridLineWidth: 1,
+        plotLines: plotLines
+    };
+
+    const yAxis = {
+        ...basicYAxis,
+        title: {
+            enabled: false,
+            text: ChartInfo.Unit,
+            textCh: ChartInfo.Unit, // 中文
+            textEn: ChartInfo.UnitEn, // 英文
+            style: {},
+            align: 'high',
+            rotation: 0,
+            y: 12,
+            x: -20,
+            textAlign: 'left',
+            reserveSpace: false
+        },
+        labels: {
+            formatter: function (ctx) {
+                return ctx.value;
+            },
+            align: 'center',
+            x: -5,
+            y: 8,
+            style: {}
+        },
+        opposite: false,
+        min: Number(ChartInfo.LeftMin || 0),
+        max: Number(ChartInfo.LeftMax || 0),
+        tickWidth: 0,
+        tickColor: '#ccc',
+        lineColor: '#000',
+        gridLineColor: '#ccc',
+        gridLineWidth: 1
+    };
+
+    const data = [];
+    const chartData = _.cloneDeep(EdbInfoList) || [];
+    chartData.forEach((item, index) => {
+        const obj = {
+            data: [],
+            type: 'line',
+            dashStyle: index === 1 ? 'Dash' : 'Solid',
+            yAxis: 0,
+            name: item.EdbName,
+            nameCh: item.EdbName,
+            nameEn: item.EdbName,
+            color: item.ChartColor, // 控制线条颜色
+            lineWidth: Number(item.ChartWidth) || 1
+        };
+        item.DataList = item.DataList || [];
+        for (const i of item.DataList) {
+            obj.data.push([i.DataTimestamp, i.Value]);
+        }
+        data.push(obj);
+    });
+
+    const legend = {
+        enabled: true,
+        align: 'left', // 图例水平对齐到左侧
+        verticalAlign: 'top', // 图例垂直对齐到顶部
+        layout: 'vertical', // 图例水平布局
+        x: 45, // 距离图表左侧的偏移量
+        y: 5, // 距离图表顶部的偏移量
+        floating: true, // 使图例悬浮在图表之上
+        backgroundColor: 'rgba(255, 255, 255, 0.8)', // 图例背景(可选)
+        borderWidth: 1, // 图例边框宽度(可选)
+        itemStyle: {
+            color: '#000', // 图例文本颜色
+            fontSize: '10px' // 图例字体大小
+        }
+    };
+
+    return {
+        chart,
+        series: data,
+        yAxis: [yAxis],
+        xAxis,
+        legend
+    };
+}
+
+// AI预测模型
+function setAIPredictChart({ DataResp, EdbInfoList, ChartInfo }) {
+    axisLimitState.leftIndex = -1;
+    axisLimitState.rightIndex = -1;
+    axisLimitState.rightTwoIndex = -1;
+
+    const isLessThanOneYear = xLabelDealHandle();
+    const xAxis = {
+        tickPosition: 'inside',
+        lineColor: '#bfbfbf',
+        labels: {
+            formatter: function (ctx) {
+                return isLessThanOneYear
+                    ? Highcharts.dateFormat('%m/%d', ctx.value)
+                    : Highcharts.dateFormat('%y/%m', ctx.value);
+            },
+            style: {}
+        }
+    };
+
+    const yAxis = {
+        ...basicYAxis,
+        title: {
+            text: ChartInfo.Unit,
+            textCh: ChartInfo.Unit, // 中文
+            textEn: ChartInfo.UnitEn, // 英文
+            style: {},
+            align: 'high',
+            rotation: 0,
+            y: -12,
+            x: 0,
+            textAlign: 'left',
+            reserveSpace: false
+        },
+        labels: {
+            formatter: function (ctx) {
+                return ctx.value;
+            },
+            align: 'center',
+            x: -5,
+            style: {}
+        },
+        opposite: false,
+        min: Number(ChartInfo.LeftMin || 0),
+        max: Number(ChartInfo.LeftMax || 0),
+        tickWidth: 1
+    };
+
+    const data = [];
+    const chartData = _.cloneDeep(EdbInfoList) || [];
+    chartData.forEach((item, index) => {
+        const obj = {
+            data: [],
+            type: 'line',
+            dashStyle: index === 1 ? 'Dash' : 'Solid',
+            yAxis: 0,
+            name: item.EdbName,
+            nameCh: item.EdbName,
+            nameEn: item.EdbName,
+            color: item.ChartColor, // 控制线条颜色
+            lineWidth: Number(item.ChartWidth) || 1,
+            marker: index > 0 ? {
+                enabled: true,
+                symbol: 'circle',
+                fillColor: "#f00",
+                radius: 3
+            } : {}
+        };
+        item.DataList = item.DataList || [];
+        for (const i of item.DataList) {
+            obj.data.push([i.DataTimestamp, i.Value]);
+        }
+        data.push(obj);
+    });
+
+    return {
+        series: data,
+        yAxis: [yAxis],
+        xAxis
+    };
+}
+
+/* 查询范围为1年内 x轴显示为月/日 否则默认年/月 */
+function xLabelDealHandle() {
+    const end =
+        year_select === 5
+            ? select_date[1]
+            : year_select === 6
+            ? new Date()
+            : '';
+
+    const year_differ = moment(end).diff(moment(select_date[0]), 'years', true);
+    // console.log(year_differ)
+    if ([5, 6].includes(year_select) && year_differ <= 1) {
+        return true;
+    } else {
+        return false;
+    }
+}
 //获取RGBA的透明度
 function parseRgbaColor(color='rgba(51, 51, 51, 1)'){
     const arr = color.match(/(\d(\.\d+)?)+/g) || ['','','',1];

+ 2 - 0
src/hooks/useReportApprove.js

@@ -16,6 +16,8 @@ export function useReportApprove(){
         const {IsReportApprove='',ReportApproveType='',IsOpenChartExpired} = etaConfigInfo.value
         isApprove.value = IsReportApprove==='true'?true:false
         isOtherApprove.value = isApprove.value&&ReportApproveType==='other' 
+        console.log('IsOpenChartExpired',IsOpenChartExpired);
+        
         isLinkChartExpired.value = IsOpenChartExpired==='true'?true:false;
     }
     //检查分类是否存在审批流

+ 2 - 1
src/views/ppt/components/ChartWrap.vue

@@ -46,7 +46,8 @@ async function getChartInfo(){
         //初始化上下限
         isUseSelfLimit.value = true
         setLimitData(res.Data)
-
+        console.log(res.Data);
+        
         chartRender({
             data:res.Data,
             renderId:renderId.value,

+ 77 - 0
src/views/report/components/reportInsert/AIForecastingModel.vue

@@ -0,0 +1,77 @@
+<script setup>
+import AIPredictModel from './AIPredictModel.vue'
+import AIPredictFrame from './AIPredictFrame.vue'
+import {ref} from 'vue'
+
+const emits=defineEmits(['update'])
+
+const AiTypeOpt=ref([
+    {
+        value:'预测模型',
+        label:'预测模型图',
+        path:'AIPredictEdb'
+    },
+    {
+        value:'模型框架',
+        label:'模型框架',
+        path:'AIPredictEdb'
+    },
+])
+
+const activeTypeAI=ref('预测模型')
+
+function handleSelectChartAI(data){
+    if (activeTypeAI.value === '预测模型') {
+        emits('update', data, 'iframe')
+    } else if (activeTypeAI.value === '模型框架') {
+        emits('update', data, 'img')
+    }
+}
+</script>
+
+
+<template>
+    <ul class="top-type-box">
+        <li 
+            :class="['item',activeTypeAI===item.value&&'active']"
+            v-for="item in AiTypeOpt" 
+            :key="item"
+            @click="activeTypeAI=item.value;list=[]"
+        >{{item.label}}</li>
+    </ul>
+    <AIPredictModel @updateList="handleSelectChartAI" v-if="activeTypeAI==='预测模型'"/>
+    <AIPredictFrame @updateList="handleSelectChartAI" v-if="activeTypeAI==='模型框架'"/>
+</template>
+
+<style lang="scss" scoped>
+.top-type-box{
+    background-color: $page-bg-grey;
+    display: flex;
+    border-bottom: 1px solid $border-color;
+    overflow-x: auto;
+    &::-webkit-scrollbar{
+        display: none;
+    }
+    .item{
+        padding: 24px 0;
+        flex: 1;
+        min-width: 105PX;
+        text-align: center;
+        border-top-right-radius: var(--van-popup-round-radius);
+        border-top-left-radius: var(--van-popup-round-radius);
+        transition: all 0.3s;
+    }
+    .active{
+        background-color: #fff;
+        color: $theme-color;
+        font-weight: 600;
+    }
+}
+@media screen and (min-width:$media-width){
+    .top-type-box{
+        .item{
+            padding: 12px 0;
+        }
+    }
+}
+</style>

+ 6 - 6
src/views/report/components/reportInsert/AIPredictFrame.vue

@@ -5,7 +5,7 @@ import apiETAForum from '@/api/etaForum'
 import { vInfiniteScroll } from '@vueuse/components'
 import { useNoAuth } from '@/hooks/useNoAuth'
 
-const emits=defineEmits(['update'])
+const emits=defineEmits(['updateList'])
 
 const searchVal=ref('')
 const listState=reactive({
@@ -59,7 +59,7 @@ watch(
     ()=>selectChartList.value,
     ()=>{
         const arr=listState.list.filter(item=>selectChartList.value.includes(item.AiPredictModelFrameworkId)).map(e=>e.FrameworkImg)
-        emits('update',arr)
+        emits('updateList',arr)
     },
     {
         immediate:true,
@@ -70,8 +70,8 @@ watch(
 
 
 <template>
-    <div class="sandTableImg-insert-wrap">
-        <van-search v-model="searchVal" shape="round" placeholder="请输入沙盘名称/品种" @search="handleRefreshList" @clear="handleRefreshList" />
+    <div class="ETA-chart-wrap">
+        <van-search v-model="searchVal" shape="round" placeholder="请输入框架名称" @search="handleRefreshList" @clear="handleRefreshList" />
         <div class="content-box">
             <div v-if="listState.list.length==0&&listState.finished">
                 <img class="list-empty-img" src="https://hzstatic.hzinsights.com/static/ETA_mobile/empty_img.png" alt="">
@@ -101,8 +101,8 @@ watch(
 </template>
 
 <style lang="scss" scoped>
-.sandTableImg-insert-wrap{
-    height: 100%;
+.ETA-chart-wrap{
+    height: calc(100% - 100px);
     display: flex;
     flex-direction: column;
     .content-box{

+ 14 - 10
src/views/report/components/reportInsert/AIPredictModel.vue

@@ -6,7 +6,7 @@ import { showToast } from 'vant'
 import { vInfiniteScroll } from '@vueuse/components'
 import { useNoAuth } from '@/hooks/useNoAuth'
 
-const emits=defineEmits(['update'])
+const emits=defineEmits(['updateList'])
 
 const searchVal=ref('')
 
@@ -34,7 +34,6 @@ async function getChartList(){
         arr.forEach(item => {
           listState.list.push({
             ...item,
-            UniqueCode:`isETAForumChart_${item.UniqueCode}`,//在获取图表详情时通过前缀区分
             HaveOperaAuth:true
           })
         });
@@ -44,7 +43,7 @@ async function getChartList(){
 getChartList()
 // 触底加载更多
 function onLoadMore(){
-    if(listState.finished||listState.loading||!listState.Keyword) return
+    if(listState.finished||listState.loading) return
     listState.page++
     getChartList()
 }
@@ -71,10 +70,11 @@ function handleSelect(item){
 watch(
     ()=>selectChartList.value,
     ()=>{
-        emits('update',selectChartList.value)
+        emits('updateList',selectChartList.value)
     },
     {
-        immediate:true
+        immediate:true,
+        deep:true
     }
 )
 
@@ -83,7 +83,7 @@ watch(
 <template>
     <div class="ETA-chart-wrap">
         <div class="sticky-box">
-            <van-search v-model="searchVal" shape="round" placeholder="请输入图名称" @search="handleRefreshList" @clear="handleRefreshList" />
+            <van-search v-model="searchVal" shape="round" placeholder="请输入预测模型图名称" @search="handleRefreshList" @clear="handleRefreshList" />
         </div>
         <div class="content-box">
             <div v-if="listState.list.length==0&&listState.finished">
@@ -115,7 +115,7 @@ watch(
 
 <style lang="scss" scoped>
 .ETA-chart-wrap{
-    height: 100%;
+    height: calc(100% - 100px);
     display: flex;
     flex-direction: column;
     .content-box{
@@ -141,13 +141,14 @@ watch(
             padding: 14px;
             box-sizing: border-box;
             position: relative;
-            max-height: 336px;
             .title{
                 font-size: 28px;
                 min-height: 60px;
             }
             img{
                 width: 100%;
+                height: 220px;
+                object-fit: contain;
             }
         }
         .active{
@@ -178,18 +179,21 @@ watch(
 @media screen and (min-width:$media-width){
     .ETA-chart-wrap{
         .chart-list{
-            
+            height: auto;
+            max-height: 100%;
             .chart-item{
                 width: 260px;
                 border-width: 1px;
                 border-radius: 2px;
                 margin-bottom: 15px;
                 padding: 7px;
-                max-height: 220px;
                 .title{
                     font-size: 14px;
                     min-height: 30px;
                 }
+                img{
+                    height: 150px;
+                }
             }
             .active{
                 svg{

+ 7 - 36
src/views/report/components/reportInsert/Index.vue

@@ -11,8 +11,7 @@ import StatisticAnalysis from './StatisticAnalysis.vue'
 import PriceChart from './PriceChart.vue'
 import BalanceSheet from './BalanceSheet/Index.vue'
 import ETAForumChart from './ETAForumChart.vue'
-import AIPredictModel from './AIPredictModel.vue'
-import AIPredictFrame from './AIPredictFrame.vue'
+import AIForecastingModel from './AIForecastingModel.vue'
 
 const emits=defineEmits(['insert'])
 
@@ -74,20 +73,8 @@ const typeOpt=ref([
         path:'semanticsPage'
     },
 ])
-const AiTypeOpt=ref([
-    {
-        value:'预测模型',
-        label:'预测模型图',
-        path:'AIPredictEdb'
-    },
-    {
-        value:'模型框架',
-        label:'模型框架',
-        path:'AIPredictEdb'
-    },
-])
+
 const activeType=ref('素材库')
-const activeTypeAI=ref('预测模型')
 async function initTypeOpts(){
     const res=await apiMenuList()
     if(res.Ret===200){
@@ -112,9 +99,10 @@ initTypeOpts()
 
 
 let list=ref([])
-
-function handleSelectChart(data){
+const AIChartType = ref('iframe')
+function handleSelectChart(data,type){
     list.value=data
+    AIChartType.value=type
 }
 
 function handleConfirmInsert(){
@@ -128,12 +116,7 @@ function handleConfirmInsert(){
     }else if(['沙盘插入','语义分析插入','素材库', ''].includes(activeType.value)){
         emits('insert',{list:list.value,type:'img',chartType:''})
     } else if(activeType.value==='AI预测模型'){
-        if (activeTypeAI.value === '预测模型') {
-            let filterList = list.value;
-            emits('insert',{list: filterList,type:'iframe',chartType:'chart'})
-        } else if (activeTypeAI.value === '模型框架') {
-            emits('insert',{list:list.value,type:'img',chartType:''})    
-        }
+        emits('insert',{list:list.value, type:AIChartType.value, chartType: AIChartType.value === 'img' ? '' : 'chart'})
     }
     
     list.value=[]
@@ -161,19 +144,7 @@ function handleConfirmInsert(){
             <PriceChart @update="handleSelectChart" v-if="activeType==='商品价格曲线'"/>
             <BalanceSheet @update="handleSelectChart" @insert="handleConfirmInsert" v-if="activeType==='平衡表'"/>
             <ETAForumChart @update="handleSelectChart" v-if="activeType==='etaForum'"/>
-
-            <div v-if="activeType==='AI预测模型'">
-                <ul class="top-type-box">
-                    <li 
-                        :class="['item',activeTypeAI===item.value&&'active']"
-                        v-for="item in AiTypeOpt" 
-                        :key="item"
-                        @click="activeTypeAI=item.value;list=[]"
-                    >{{item.label}}</li>
-                </ul>
-                <AIPredictModel @update="handleSelectChart" v-if="activeTypeAI==='预测模型'"/>
-                <AIPredictFrame @update="handleSelectChart" v-if="activeTypeAI==='模型框架'"/>
-            </div>
+            <AIForecastingModel @update="handleSelectChart" v-if="activeType==='AI预测模型'"/>
         </div>
         <div class="bot-btn" v-if="activeType!=='平衡表'">
             <van-button type="primary" block @click="handleConfirmInsert" :disabled="list.length===0">插入</van-button>