浏览代码

Merge branch 'master' into 2.0

bding 10 月之前
父节点
当前提交
d4db62cfe2

+ 1 - 1
index.html

@@ -3,7 +3,7 @@
   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
-    <title>弘则研究</title>
+    <title></title>
     <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
     <script src="/jquery-3.6.0.min.js"></script>
   </head>

+ 1 - 0
package.json

@@ -10,6 +10,7 @@
   "dependencies": {
     "@fullcalendar/core": "^5.10.1",
     "@fullcalendar/interaction": "^5.10.1",
+    "@fullcalendar/daygrid": "^5.10.1",
     "@fullcalendar/timegrid": "^5.10.1",
     "@fullcalendar/vue3": "^5.10.1",
     "@vant/touch-emulator": "^1.3.2",

+ 78 - 0
src/api/hzyb/forexCalendar.js

@@ -0,0 +1,78 @@
+//外汇日历表
+
+import {get,post} from './http'
+
+/**
+ * 获取基础指标详情
+ * @param {Object} params 
+ * @param {*} params.edb_info_id 指标id
+ * @param {*} params.date_type 默认10
+ * @param {*} params.start_date 默认空
+ * @param {*} params.end_date 默认空
+ * @param {*} params.start_year 默认空
+ * @returns 
+ */
+export const apiGetbaseEdbInfo=params=>{
+    return get('/edb_info/data',{...params,...{date_type:10,start_date:'',end_date:'',start_year:''}})
+}
+
+/**
+ * 获取预测指标详情
+ * @param {Object} params 
+ * @param {*} params.edb_info_id 指标id
+ * @param {*} params.date_type 默认10
+ * @param {*} params.start_date 默认空
+ * @param {*} params.end_date 默认空
+ * @param {*} params.start_year 默认5
+ * @param {*} params.chart_type 默认1
+ * @param {*} params.calendar 默认公历
+ * @returns 
+ */
+export const apiGetpredictEdbInfo=params=>{
+    return get('/predict_edb_info/data',{...params,...{date_type:10,start_date:'',end_date:'',start_year:5,chart_type:1,calendar:'公历'}})
+}
+
+/**
+     * 获取日期范围内事项列表
+     * @param {Object} params 
+     * @param {Number} params.chart_permission_id 品种ID
+     * @param {String} params.start_date 起始时间
+     * @param {String} params.end_date 终止时间
+     * @returns [{
+     *      Date:"2024-03-28",//事项日期
+     *      Matters:[],//事项详情
+     * }]
+     */
+export const apiGetCalendarEventList = (params)=>{
+    return get('/fe_calendar/matter/list',params)
+}
+
+/**
+ * 获取品种列表
+ * @param {*} params 
+ * @returns 
+ */
+export const apiGetPermissionList = (params)=>{
+    return get('/fe_calendar/permission/list',params)
+}
+
+/**
+ * 获取品种最新日历月份
+ * @param {Object} params 
+ * @param {Number} params.chart_permission_id 品种ID
+ * @returns 
+ */
+export const apiGetPermissionNewestDate = (params)=>{
+    return get('/fe_calendar/permission/latest_month',params)
+}
+
+/**
+     * 获取指定日期事项列表
+     * @param {Object} params 
+     * @param {Number} params.chart_permission_id 品种ID
+     * @param {String} params.matter_date "2024-03-29" 指定日期
+     * @returns
+     */
+export const apiGetDailyEventList = (params)=>{
+    return get('/fe_calendar/matter/detail',params)
+}

+ 25 - 0
src/api/hzyb/report.js

@@ -74,4 +74,29 @@ export const apiPublicBannerMark = params=>{
  */
 export const apiPublicBannerList = params=>{
     return get('/public/banner/list',params)
+}
+
+/**
+ * banner历史图列表
+ * @returns 
+ */
+export const bannerHistoryList = params=>{
+    return get('/public/banner_history/list',params)
+}
+
+/**
+ * banner图获取报名二维码
+ * @returns 
+ */
+export const getBannerQrcode = params=>{
+    return get('/public/banner/get_qrcode',params)
+}
+
+
+/**
+ * banner图详情
+ * @returns 
+ */
+export const getBannerDetail = params=>{
+    return get('/public/banner/detail',params)
 }

+ 34 - 1
src/router/hzyb/index.js

@@ -67,5 +67,38 @@ export const hzybRoutes=[
                 component:() => import("@/views/hzyb/report/PreviewPDF.vue")
             }
         ]
-    }
+    },
+    {
+        path:'/hzyb/surveyHistory',
+        name:'surveyHistory',
+        component: () => import("@/views/hzyb/report/surveyHistory.vue"),
+    },
+    //外汇日历模块
+    {
+        path:'/hzyb/forex',
+        name:'hzybForex',
+        component:()=>import("@/App.vue"),
+        children:[
+            {
+                path:'transindex', //iframe嵌套页
+                name:'hzybTransForexIndex',
+                component:()=> import("@/views/hzyb/forexCalendar/transIndex.vue")
+            },
+            {
+                path:'index', //日历页面
+                name:'hzybForexIndex',
+                component:()=> import("@/views/hzyb/forexCalendar/Index.vue")
+            },
+            {
+                path:'detail', //图表详情页
+                name:'hzybForexDetail',
+                component:()=> import("@/views/hzyb/forexCalendar/Detail.vue")
+            }
+        ]
+    },
+    {
+        path:'/hzyb/surveyDetail',
+        name:'surveyDetail',
+        component: () => import("@/views/hzyb/report/surveyDetail.vue"),
+    },
 ]

+ 1 - 1
src/views/hzyb/chart/Detail.vue

@@ -320,7 +320,7 @@ const getChartInfo=async (type)=>{
     loading.value=true
 
     //通用类根据code获取详情
-    if(chartSource!==1) return getCommonChartDetail();
+    if(![1,11].includes(chartSource)) return getCommonChartDetail();
 
     let res=null
     // 如果是从我的图库中来的

+ 6 - 2
src/views/hzyb/chart/component/chartBox.vue

@@ -105,7 +105,11 @@ const chartDefaultOpts={
 
 const props = defineProps({
   options: Object,
-	chartInfo: Object
+	chartInfo: Object,
+    showTitle:{
+        type:Boolean,
+        default:true
+    }
 })
 import {ref,onMounted,watch,toRefs } from 'vue'
 import Highcharts from 'highcharts/highstock';
@@ -187,7 +191,7 @@ watch(
 
 <template>
     <div class="chart-wrap">
-        <div class="chart-top-labels">
+        <div class="chart-top-labels" v-if="props.showTitle">
             <div 
                 class="item" 
                 v-for="(item,index) in props.options.series" 

+ 195 - 0
src/views/hzyb/forexCalendar/Detail.vue

@@ -0,0 +1,195 @@
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import {useRoute, useRouter,onBeforeRouteUpdate} from 'vue-router'
+import chartBox from '../chart/component/chartBox.vue'
+import {
+    apiGetbaseEdbInfo,apiGetpredictEdbInfo,apiGetCalendarEventList
+} from '@/api/hzyb/forexCalendar.js'
+import {setSplineOpt,setOptions} from '../hooks/chartBase'
+/**
+ * 外汇日历详情
+        传入参数:
+            选中的指标
+            月份的起始日期,结束日期
+        获取所有事项
+        找到带指标的事项,设置初始索引为选中的指标在所有指标事项中的位置
+        切换上下张切换索引,当到达第一张/最后一张时,循环这个列表
+ */
+const route=useRoute()
+let EdbList = ref([])
+let currentEdbIndex=ref(0) //当前展示的指标索引
+let currentEdbId=ref('') //当前展示的指标id
+let chartData=ref({
+    series:[],
+    xAxis:[],
+    yAxis:[],
+
+})// 图表配置数据
+let resData=ref({
+    EdbInfo:{}
+})//接口详情数据
+let pageBoxPosition=reactive({
+    top:window.innerHeight-165,
+    left:window.innerWidth-50,
+    temTop:0,
+    temLeft:0,
+})
+localStorage.setItem('hzyb-token',route.query.token)
+
+function pageChange(type){
+    const length = EdbList.value.length
+    if(type==='before'){
+        if(currentEdbIndex.value===0){
+            currentEdbIndex.value = length-1
+        }else{
+            currentEdbIndex.value = currentEdbIndex.value - 1
+        }
+    }else{
+        if(currentEdbIndex.value===length-1){
+            currentEdbIndex.value = 0
+        }else{
+            currentEdbIndex.value = currentEdbIndex.value + 1
+        }
+    }
+    currentEdbId.value = EdbList.value[currentEdbIndex.value].edbInfoId
+    getEdbDetail()
+}
+const pageTouchmove=(e)=>{
+    const touchObj=e.touches[0]
+    let top=touchObj.clientY-82
+    let left=touchObj.clientX-25
+    if(left<=0){
+        left=0
+    }
+    if(left>window.innerWidth-50){
+        left=window.innerWidth-50
+    }
+    if(top<=0){
+        top=0
+    }
+    if(top>window.innerHeight-115){
+        top=window.innerHeight-115
+    }
+
+    pageBoxPosition.top=top
+    pageBoxPosition.left=left
+
+    event.preventDefault();//阻止页面移动
+}
+
+//获取当月指标列表
+async function getEdbList(){
+    const {startDate,endDate,edbInfoId,permissionId,permissionName} = route.query
+    document.title = startDate.slice(0,-3)+permissionName+'事项详情'
+    await apiGetCalendarEventList({
+        chart_permission_id:Number(permissionId),
+        start_date:startDate,
+        end_date:endDate
+    }).then(res=>{
+        if(res.code!==200) return 
+        let events = (res.data?res.data:[]).map(dailyEvents=>{
+            return dailyEvents.matters
+        })
+        events = events.flat().filter(e=>e.matter_type!==1).map(e=>{
+            return {
+                edbInfoId:e.edb_info_id,
+                matterType:e.matter_type
+            }
+        })
+        EdbList.value = events
+        currentEdbId.value = Number(edbInfoId)
+        const eventIndex = events.findIndex(e=>e.edbInfoId===Number(edbInfoId))
+        currentEdbIndex.value = eventIndex>=0?eventIndex:0
+        getEdbDetail()
+    })
+}
+getEdbList()
+
+function capitalizeFirstLetter(obj) {
+  return Object.keys(obj).reduce((acc, key) => {
+    const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
+    acc[capitalizedKey] = obj[key];
+    return acc;
+  }, {});
+}
+async function getEdbDetail(){
+    const res = EdbList.value[currentEdbIndex.value].matterType===2
+                ?await apiGetbaseEdbInfo({edb_info_id:currentEdbId.value})
+                :await apiGetpredictEdbInfo({edb_info_id:currentEdbId.value})
+    resData.value=res.data
+    //由于EdbInfo大小写命名和后台不一致,重新整一份
+    const EdbInfo = capitalizeFirstLetter(res.data.EdbInfo)
+    const edbData = {
+        DataList:res.data.DataList||[],
+        EdbInfo:{
+            ...EdbInfo,
+            ChartColor:'',
+            ChartStyle:'',
+            PredictChartColor:'',
+            ChartType:0,
+            ChartWidth:3,
+            MaxData:res.data.EdbInfo.maxValue,
+            MinData:res.data.EdbInfo.minValue,
+            EdbInfoCategoryType:1,
+            EdbInfoType:0,
+            IsAxis:1,
+        }
+    }
+
+    chartData.value = EdbList.value[currentEdbIndex.value].matterType===2
+                      ? setOptions(res.data.DataList||[],EdbInfo)
+                      : setSplineOpt([{
+                            ...edbData.EdbInfo,
+                            DataList:res.data.DataList||[]
+                            }],
+                            {value:{ChartInfo:{ChartType:1}}}
+                        )
+}
+</script>
+
+<template>
+    <div class="edb-detail-wrap">
+        <div class="edb-title">{{resData.EdbInfo.edbName}}</div>
+        <chartBox :options='chartData' :chartInfo="{ChartType:1}" :showTitle="false"></chartBox>
+        <!-- 上一张下一张图切换 -->
+        <div 
+            class="change-page-wrap" 
+            :style="{left:pageBoxPosition.left+'px',top:pageBoxPosition.top+'px'}"
+            @touchmove.stop="pageTouchmove"
+            @touchstart.stop=""
+        >
+            <div class="top" @click.stop="pageChange('before')"></div>
+            <div class="bot" @click.stop="pageChange('next')"></div>
+        </div>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.edb-detail-wrap{
+    .edb-title{
+        padding: 40px 34px 20px 34px;
+        font-size: 32px;
+        font-weight: bold;
+        color: #1F243A;
+        letter-spacing: 2px;
+        @media (min-width: 768px){
+            padding: 0px 0 20px 0;
+            font-size: 18px;
+        }
+    }
+    .change-page-wrap{
+        position: fixed;
+        right: 0;
+        bottom: 50px;
+        width: 50PX;
+        height: 115PX;
+        background-image: url('../../../assets/hzyb/chart/before-next.png');
+        background-size: cover;
+        z-index: 10;
+        .top,.bot{
+            height: 50%;
+        }
+
+    }
+}
+</style>

+ 327 - 0
src/views/hzyb/forexCalendar/Index.vue

@@ -0,0 +1,327 @@
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import {Popup,DatetimePicker,TreeSelect,Icon as VanIcon,Toast } from 'vant'
+import {apiGetPermissionList,apiGetPermissionNewestDate,apiGetCalendarEventList,apiGetDailyEventList}  from '@/api/hzyb/forexCalendar.js'
+import moment from 'moment'
+import BaseCalendar from './components/BaseCalendar.vue'
+
+let baseCalendarRef = ref(null)
+let currentDay = ref('') //选择的日期
+function dateClick(info){
+    setEventInfo(info.dateStr)
+}
+function eventClick(info){
+    setEventInfo(info.event.startStr)
+}
+//事项列表
+let eventList = ref([])
+let showEventList = ref(false)
+async function setEventInfo(date){
+    if(!permissionValue.value) return Toast('请选择品种!')
+    currentDay.value = date||''
+    //获取当天的日历信息,若有事项,则打开弹窗
+    await getEventsByDay()
+    showEventList.value = Boolean(eventList.value.length)
+}
+async function getEventsByDay(){
+    await apiGetDailyEventList({
+        chart_permission_id:Number(permissionValue.value),
+        matter_date:currentDay.value
+    }).then(res=>{
+        if(res.code!==200){
+            eventList.value=[]
+            return 
+        }
+        eventList.value = res.data||[]
+    })
+}
+
+let monthValue = ref(null)
+let datePickShow = ref(false)
+function changeMonth(month){
+    const Month = month||new Date()
+    baseCalendarRef.value?.calendarApi.gotoDate(Month)
+    renderCalendar()
+}
+
+let typePickShow = ref(false)
+let activeId = ref(0)
+let activeIndex = ref(0)
+let items = ref([]) //vant品种列表
+
+//选择一级品种,没用到
+function clickFirstHandle(index){}
+//选择二级品种
+function clickItemHandle({text,id}){
+    //获取到品种ID
+    if(permissionValue.value===id){
+        typePickShow.value = false
+        return 
+    }
+    permissionValue.value = id
+    permissionName.value = text
+    changePermission()
+}
+//获取该品种最新月份
+async function changePermission(){
+    if(!hasAuth.value) return 
+    if(!permissionValue.value) return 
+    await getPermissionNewestMonth()
+}
+async function getPermissionNewestMonth(){
+    //获取最新月份的接口
+    await apiGetPermissionNewestDate({
+        chart_permission_id:Number(permissionValue.value)
+    }).then(res=>{
+        if(res.code!==200) return 
+        monthValue.value = moment(res.data?(res.data+'-01'):new Date())._d
+        changeMonth(monthValue.value)
+    })
+}
+
+//权限控制
+let hasAuth = ref(true)
+let noAuth = ref({
+    type:'',
+    name:'',
+    mobile:'',
+    customer_info:{}
+})
+let permissonList = ref([])
+let permissionValue = ref('') //选中的品种ID
+let permissionName = ref('') //品种名称
+function getPermissionList(){
+    apiGetPermissionList().then(res=>{
+        if(res.code===200){
+            hasAuth.value = true
+            permissonList.value = res.data||[]
+            //将品种列表转换成vant-TreeSelect支持的格式
+            items.value = permissonList.value.map(p=>{
+                p.text = p.chart_permission_name
+                if(p.children.length){
+                    p.children = p.children.map(c=>{
+                        c.text = c.chart_permission_name
+                        c.id = c.chart_permission_id
+                        return c
+                    })
+                }
+                return p
+            })
+        }else{
+            hasAuth.value = false
+            noAuth.value = res.data
+            //将无权限信息传到transIndex,transIndex显示无权限
+            window.parent.postMessage(JSON.stringify({
+                path:'/hzyb/forex/noauth',
+                query:{
+                   noAuth:noAuth.value
+                }
+            }),'*')
+        }
+    })
+}
+async function renderCalendar(){
+    typePickShow.value = false
+    datePickShow.value = false
+
+    //获取当前日历的起始日期+终止日期,可能会跨月
+    const {activeStart,activeEnd} = baseCalendarRef.value?.calendarApi.view||{}
+    const res = await apiGetCalendarEventList({
+        chart_permission_id:Number(permissionValue.value),
+        start_date:moment(activeStart).format('YYYY-MM-DD'),
+        end_date:moment(activeEnd).subtract(1, 'days').format('YYYY-MM-DD'), //activeEnd到最后一天的24:00会被认为是第二天,所以取前一天
+    })
+    if(res.code!==200) return 
+    const allEventSource = baseCalendarRef.value?.calendarApi.getEventSources()||[]
+    //先清空所有eventSource,再添加
+    allEventSource.forEach(es=>es.remove())
+    //将日历表每天的事件作为一个eventSource
+    const eventList = (res.data?res.data:[]).map(dailyEvents=>{
+        const eventSource = { id:dailyEvents.Date,events:[]}
+        eventSource.events = dailyEvents.matters.map(e=>{
+            return {
+                ...e,
+                start:e.matter_date,
+                title:e.title,
+                textColor:e.font_color||'#000',
+                backgroundColor:e.filling_color||'#fff',
+                borderColor:e.filling_color||'#fff',
+            }
+        })
+        //移动端要求每天最多展示2条
+        if(eventSource.events.length>2){
+            eventSource.events = eventSource.events.splice(0,2)
+        }
+        return eventSource
+    })
+    eventList.forEach(es=>{
+        baseCalendarRef.value?.calendarApi.addEventSource(es)
+    })
+    baseCalendarRef.value?.calendarApi.updateSize()
+}
+//跳转事项详情
+function handleClickEvent(item){
+    if(item.matter_type===1) return Toast("该事项未关联指标")
+    const {activeStart,activeEnd} = baseCalendarRef.value?.calendarApi.view||{}
+    //信息传到transIndex,在transIndex跳转
+    window.parent.postMessage({
+        path:'/hzyb/forex/detail',
+        query:{
+            startDate:moment(activeStart).format('YYYY-MM-DD'),
+            endDate:moment(activeEnd).subtract(1, 'days').format('YYYY-MM-DD'),
+            edbInfoId:item.edb_info_id,
+            permissionId:permissionValue.value,
+            permissionName:permissionName.value,
+            token:localStorage.getItem('hzyb-token')||''
+        }
+    },'*')
+    showEventList.value = false
+}
+onMounted(()=>{
+    monthValue.value = new Date()
+    getPermissionList()
+})
+</script>
+
+<template>
+    <div class="forex-calendar-wrap">
+        <div class="calendar-header">
+            <span @click.stop="datePickShow = true"><img src="@/assets/hzyb/chart/calendar.png">选择日期</span>
+            <span @click.stop="typePickShow = true"><van-icon name="bars" color="#E3B377" size="16"/>其他品种</span>
+        </div>
+        <BaseCalendar 
+            ref="baseCalendarRef"
+            :markText="{date:monthValue,type:permissionName||'请选择品种'}"
+            @dateClick="dateClick"
+            @eventClick="eventClick"
+        ></BaseCalendar>
+        <!-- 选择日期 -->
+        <Popup v-model:show="datePickShow" position="bottom">
+            <DatetimePicker
+                v-model="monthValue"
+                title="选择日期"
+                type="year-month"
+                :formatter="(type, val)=>{
+                    if (type === 'year') {
+                        return `${val}年`;
+                    } else if (type === 'month') {
+                        return `${val}月`;
+                    }
+                    return val;
+                }"
+                @confirm="changeMonth"
+                @cancel="datePickShow = false"
+            />
+        </Popup>
+        <!-- 选择品种 -->
+        <Popup v-model:show="typePickShow" position="bottom">
+            <!-- <div class="select-header">
+                <span class="cancel" @click="typePickShow=false">取消</span>
+                <span class="ensure" @click="changeType">确认</span>
+            </div> -->
+            <TreeSelect
+                v-model:active-id="activeId"
+                v-model:main-active-index="activeIndex"
+                :items="items"
+                @click-nav="clickFirstHandle" 
+                @click-item="clickItemHandle" 
+            />
+        </Popup>
+        <Popup v-model:show="showEventList" position="bottom" :style="{ height: '50%' }">
+            <div class="pop-wrap">
+                <div class="select-header">
+                    <span class="title">{{ currentDay }}{{ permissionName }}事项</span>
+                    <span class="btn" @click="showEventList=false"><van-icon name="cross" size="16"/></span>
+                </div>
+                <div class="event-list-wrap">
+                    <ul class="event-list">
+                        <li class="event-item" v-for="(item,index) in eventList" :key="index" @click="handleClickEvent(item)">
+                            {{item.title}}
+                        </li>
+                    </ul>
+                </div>
+            </div>
+        </Popup>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.forex-calendar-wrap{
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    left:0;
+    right:0;
+    top:0;
+    bottom:0;
+    .calendar-header{
+        font-size: 16px;
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+        height:40px;
+        span{
+            margin-right: 40px;
+            display: flex;
+            align-items: center;
+            gap:5px;
+            i{
+                font-size: 18px !important;
+            }
+            img{
+                width:15px;
+                height:15px;
+            }
+        }
+    }
+    .pop-wrap{
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+        .select-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: var(--van-picker-action-padding);
+            font-size: var(--van-picker-action-font-size);
+            height: var(--van-picker-toolbar-height);
+            .cancel,.btn{
+                color: #969799;
+            }
+            .ensure {
+                color: #576b95;
+            }
+            .title{
+                font-weight: bold;
+            }
+        }
+        .event-list-wrap{
+            font-size: var(--van-picker-action-font-size);
+            padding: var(--van-padding-md);
+            flex:1;
+            overflow-y: auto;
+            .event-item{
+                margin-bottom: var(--van-padding-md);
+                border-bottom: 1px solid #E7E7E7;
+            }
+        }
+    }
+    
+    .van-tree-select {
+        ::v-deep(.van-sidebar-item--select:before) {
+            background-color: #E3B377;
+        }
+
+        ::v-deep(.van-tree-select__item--active) {
+            color: #E3B377;
+        }
+
+        ::v-deep(.van-tree-select__selected) {
+            position: absolute !important;
+        }
+    }
+    
+}
+</style>

+ 189 - 0
src/views/hzyb/forexCalendar/components/BaseCalendar.vue

@@ -0,0 +1,189 @@
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+
+import '@fullcalendar/core/vdom'; // solve problem with Vite
+import FullCalendar from "@fullcalendar/vue3";
+import dayGridPlugin from '@fullcalendar/daygrid'
+import interactionPlugin from '@fullcalendar/interaction'
+import moment from 'moment'
+
+const props = defineProps({
+    showMark:{
+        type:Boolean,
+        default:true
+    },
+    markText:{
+        type:Object,
+        default:{
+            date:'2024-04',
+            type:'当前品种'
+        }
+    },
+
+})
+const emit = defineEmits(["dateClick","eventClick"])
+let FullCalendarRef = ref(null)
+let calendarApi = ref(null)
+let calendarOptions = ref({
+    height:'100%',
+    locale: "zh-cn",
+    plugins: [ dayGridPlugin, interactionPlugin ],
+    initialView:'dayGridMonth',
+    headerToolbar:false,//不显示头部信息
+    weekNumberCalculation:'ISO',//从周一开始
+    dayHeaderFormat:{ //https://fullcalendar.io/docs/v5/date-formatting
+        weekday:'narrow', //头部星期显示为一 二 三...
+    },
+    fixedWeekCount:false,
+    eventOrder:'sort',
+    dayCellContent:function(arg){ //单元格日期显示为 1 2 3...
+        return arg.date.getDate()
+    },
+    dateClick:handleDateClick,
+    eventClick:handleEventClick
+})
+function handleDateClick(info){
+    emit("dateClick",info)
+}
+function handleEventClick(info){
+    emit("eventClick",info)
+}
+
+onMounted(()=>{
+    calendarApi.value = FullCalendarRef.value.getApi()
+
+})
+defineExpose({ calendarApi });
+</script>
+
+<template>
+    <div class="base-calendar-wrap">
+        <FullCalendar :options="calendarOptions" ref="FullCalendarRef" class="full-calendar-wrap">
+            <template #eventContent="arg">
+                <div class="popper-content"
+                    :style="{fontWeight:arg.event.extendedProps.font_bold?'bold':'normal'}"
+                >
+                   {{arg.event.title||''}}
+                </div>
+            </template>
+        </FullCalendar>
+        <div class="water-mark" v-if="showMark">
+            <p>{{ moment(markText.date).format("YYYY-MM") }}</p>
+            <p>{{ markText.type }}</p>
+        </div>
+    </div>
+    
+</template>
+
+<style scoped lang="scss">
+.base-calendar-wrap{
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    position:relative;
+    .full-calendar-wrap{
+        z-index: 2;
+        flex: 1;
+        :deep(.fc-daygrid-day-top){ //日期偏左显示
+            flex-direction: row;
+        }
+        :deep(.fc-daygrid-event){
+            border-radius: 0;
+            margin:0 !important;
+        }
+        /* :deep(.fc-col-header){
+            width:100% !important;
+        }
+        :deep(.fc-daygrid-body){
+            width:100% !important;
+        }
+        :deep(.fc-scrollgrid-sync-table){
+            width:100% !important;
+        } */
+        :deep(.fc-daygrid-day-number){
+            font-size: 12px;
+            line-height: 12px;
+            padding:0 2px;
+        }
+        :deep(.fc-scrollgrid-sync-inner){
+            width: 100%;
+            height: 100%;
+            display: flex;
+            justify-content: center;
+        }
+        :deep(.fc-daygrid-day-frame.fc-scrollgrid-sync-inner){
+            display: block;
+        }
+        :deep(.fc-col-header-cell-cushion ){
+            font-size: 12px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+        :deep(.fc-scroller){
+            overflow:auto !important; //x,y轴在横屏情况下会对调,默认可滚动
+            &::-webkit-scrollbar{
+                width: 0;
+            }
+        }
+        :deep(.fc-scrollgrid){
+            border:none;
+            .fc-scrollgrid-section-header{
+                th{
+                    border:none !important;
+                }
+                .fc-col-header-cell{
+                    //font-weight: 500;
+                    .fc-scrollgrid-sync-inner{
+                        justify-content: flex-start;
+                    }
+                }
+            }
+            .fc-scrollgrid-section-body{
+                td{
+                    border: none !important;
+                    border-left:3px solid #E3B377 !important;
+                }
+            }
+        }
+        :deep(.fc-daygrid-day-events){
+            min-height: 32px !important;
+            margin-top: 0 !important;
+        }
+        :deep(.fc-more-popover){
+            display:none !important;
+        }
+        :deep(.fc-daygrid-day-bottom){
+            font-size: 12px;
+            display: none;
+        }
+        :deep(.fc-daygrid-more-link.fc-more-link){
+            font-size: 12px;
+        }
+        .popper-content{
+            font-size: 12px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            /* height: 16px; */
+        }
+    }
+    .water-mark{
+        z-index: 1;
+        position: absolute;
+        top:0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        font-size: 64px;
+        opacity: 0.3;
+        p{
+            margin:0;
+            padding:0;
+        }
+    }
+}
+</style>

+ 150 - 0
src/views/hzyb/forexCalendar/components/NoAuth.vue

@@ -0,0 +1,150 @@
+<script setup>
+import { computed,ref } from "vue";
+import { Dialog } from "vant";
+import {apiApplyPermission,apiUserInfo} from '@/api/hzyb/user'
+
+let userInfo=ref(null)
+const getUserInfo=async ()=>{
+  const res=await apiUserInfo()
+  if(res.code===200){
+    userInfo.value=res.data
+  }
+}
+getUserInfo()
+
+
+const props = defineProps({
+  data: Object,
+});
+
+const authType = computed(() => {
+    if(!props.data) return
+    if (props.data.type === "contact") {
+        handleAutoApply()
+        return 1;
+    }
+    if (props.data.type === "expired") {
+        return 2;
+    }
+    if (props.data.type === "apply" && !props.data.customer_info.has_apply) {
+        return 3;
+    }
+    if (props.data.type === "apply" && props.data.customer_info.has_apply) {
+        return 4;
+    }
+});
+
+const handleAutoApply=()=>{
+  if(!props.data.customer_info.has_apply){
+    if(props.data.customer_info.status=='冻结'||(props.data.customer_info.status=='试用'&&props.data.customer_info.is_suspend==1)){
+        apiApplyPermission({
+            company_name:props.data.customer_info.company_name,
+            real_name:props.data.customer_info.name,
+            source:11,
+            from_page:'事件日历'
+        }).then(res=>{
+            if(res.code===200){
+                console.log('主动申请成功');
+            }
+        }) 
+    }
+  }
+}
+
+// 点击申请
+const handleApply=()=>{
+    if(userInfo.value.is_bind===0){
+      Dialog.confirm({
+        title:'温馨提示',
+        message:'为了优化您的用户体验,\n 请登录后查看更多信息!',
+        confirmButtonText:'去登录',
+        confirmButtonColor:'#E6B77D',
+        cancelButtonColor:'#666'
+      }).then(res=>{
+        wx.miniProgram.reLaunch({url:'/pages/login'})
+      })
+      return
+    }
+
+
+    if(props.data.customer_info.status=='流失'||props.data.customer_info.status=='关闭'){
+        apiApplyPermission({
+            company_name:props.data.customer_info.company_name,
+            real_name:props.data.customer_info.name,
+            source:11,
+            from_page:'事件日历'
+        }).then(res=>{
+            wx.miniProgram.navigateTo({url:'/pages-applyPermission/applyResult'})
+        })
+        return
+    }
+    wx.miniProgram.navigateTo({ url: '/pages-applyPermission/applyPermission?source=11&from_page=事件日历' })
+}
+
+const goBack=()=>{
+  wx.miniProgram.switchTab({url:'/pages/report/report'})
+}
+</script>
+
+<template>
+  <div class="chart-noauth-wrap">
+    <img class="img" src="https://hzstatic.hzinsights.com/static/icon/hzyb/activity_no_auth.png" alt="" v-if="authType!=4" />
+    <img class="img-wait" src="https://hzstatic.hzinsights.com/static/icon/hzyb/chart_wait.png" alt="" v-else />
+    <block v-if="authType == 1">
+      <div style="margin-bottom: 15px">您暂无权限查看事件日历</div>
+      <div>若想查看请联系对口销售</div>
+      <a :href="'tel:'+props.data.mobile" tag="div" class="global-btn-yellow-change btn" style="margin-top: 30px">联系销售</a>
+    </block>
+
+    <block v-if="authType == 2">
+      <div style="margin-bottom: 15px">您的权限已到期,暂时无法查看事件日历</div>
+      <div>若想继续查看请联系对口销售</div>
+      <!-- <div>{{info.name}}:{{info.mobile}}</div> -->
+      <a :href="'tel:'+props.data.mobile" tag="div" class="global-btn-yellow-change btn" style="margin-top: 30px">联系销售</a>
+    </block>
+
+    <block v-if="authType == 3">
+      <div style="margin-bottom: 15px">您暂无权限查看事件日历</div>
+      <div>若想查看可以申请开通</div>
+      <div class="global-btn-yellow-change btn" style="margin-top: 30px" @click="handleApply">立即申请</div>
+    </block>
+
+    <block v-if="authType == 4">
+      <div style="margin-bottom: 15px">您已提交申请</div>
+      <div>请等待销售人员与您联系</div>
+      <div class="global-btn-yellow-change btn" style="margin-top: 30px" @click="goBack">返回</div>
+    </block>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.chart-noauth-wrap {
+  padding-top: 50px;
+  text-align: center;
+  font-size: 32px;
+  .img {
+    width: 100%;
+    margin-bottom: 50px;
+  }
+  .img-wait {
+    margin-top: 200px;
+    width: 186px;
+    margin-bottom: 50px;
+  }
+  .global-btn-yellow-change{
+    background: linear-gradient(270deg, #EEC795 0%, #D9A360 100%);
+    border-radius: 35px;
+    color: #fff;
+    text-align: center;
+    line-height: 70px;
+  }
+  .btn{
+    width: 380px;
+    line-height: 7rpx;
+    margin-left: auto;
+    margin-right: auto;
+    margin-top: 40px;
+    display: block;
+  }
+}
+</style>

+ 109 - 0
src/views/hzyb/forexCalendar/transIndex.vue

@@ -0,0 +1,109 @@
+<script setup>
+import { ref, reactive, onMounted,watch,nextTick } from 'vue'
+import {useRoute,useRouter} from 'vue-router'
+import NoAuth from './components/NoAuth.vue'
+//强制横屏
+const forceLandscape = (id = '.iframe-class') => {
+  const handler = () => {
+    let width = document.documentElement.clientWidth;
+    let height = document.documentElement.clientHeight;
+    let targetDom = document.querySelector(id);
+    if (!targetDom) return;
+    
+    if (width > height) {
+      targetDom.style.position = 'absolute';
+      targetDom.style.width = `${width}px`;
+      targetDom.style.height = `${height}px`;
+      targetDom.style.left = `${0}px`;
+      targetDom.style.top = `${0}px`;
+      targetDom.style.transform = 'none';
+      targetDom.style.transformOrigin = '50% 50%';
+    } else {
+      targetDom.style.position = 'absolute';
+      targetDom.style.width = `${height}px`;
+      targetDom.style.height = `${width}px`;
+      targetDom.style.left = `${0 - (height - width) / 2}px`;
+      targetDom.style.top = `${(height - width) / 2}px`;
+      targetDom.style.transform = 'rotate(90deg)';
+      targetDom.style.transformOrigin = '50% 50%';
+    }
+  };
+
+  const handleResize = () => {
+    setTimeout(() => {
+      handler();
+    }, 300);
+  };
+
+  window.addEventListener('resize', handleResize);
+
+  handler();
+};
+const router = useRouter()
+function changeRoute(msg){
+    let data
+    try{
+        data = typeof msg.data === 'string'?JSON.parse(msg.data):msg.data||{}
+    }catch(e){
+        data = {}
+    }
+    const {path,query} = data
+
+    if(!path||!query) return //vue-devtools也会发message
+
+    if(path.includes('noauth')){
+        noAuth.value = query.noAuth
+        hasAuth.value = false
+        return
+    }
+    router.push({
+        path,
+        query
+    })
+
+}
+let hasAuth = ref(true)
+let noAuth = ref({
+    "name": "俞梦涯", //销售名称
+    "mobile": "15988911628", //销售电话
+    "type": "contact", //类型:
+    "hz_phone": "", //弘则公司电话
+    "customer_info": {
+    "company_name": "江苏智谋科技有限公司", //客户公司名称
+    "name": "孙浩", //用户姓名
+    "mobile": "18883968626", //用户手机号
+    "status": "冻结", //客户状态
+    "is_suspend": 0, //是否暂停:0-否;1-是
+    "has_apply": false //是否有申请过
+    }
+})
+watch(hasAuth,(newVal,oldVal)=>{
+    if(newVal){
+        nextTick(()=>{
+            window.dispatchEvent(new Event('resize'))
+        })
+        
+    }
+})
+
+const iframeURLBase = ref(process.env.NODE_ENV === 'development'?'/xcx_h5':'')
+const route = useRoute()
+onMounted(()=>{
+    localStorage.setItem('hzyb-token',route.query.token)
+    forceLandscape()
+    window.addEventListener('message',changeRoute)
+})
+</script>
+
+<template>
+    <!-- 直接将旋转作用在Index.vue上会导致日历插件dateClick不触发,vant组件滑动手势x,y轴相反 -->
+    <!-- 包一层,iframe为可替换元素,内部元素不受父级样式影响,但overflow-x,overflow-y判断会对调 -->
+    <iframe :src="iframeURLBase+'/hzyb/forex/index'" class="iframe-class" v-if="hasAuth"></iframe>
+    <NoAuth v-else :data="noAuth"></NoAuth>
+</template>
+
+<style scoped lang="scss">
+.iframe-class{
+    border:0;
+}
+</style>

+ 436 - 0
src/views/hzyb/hooks/chartBase.js

@@ -0,0 +1,436 @@
+//图表数据处理 公共函数
+/**
+ * 为了应对之后其他页面需要渲染Highcharts图表的情况
+ * 基于 hzyb/chart/Detail.vue 内的函数进一步封装
+ * 非/---/括起来的部分表示对原函数有调整,/---/括起来的部分是直接复制的
+ */
+import moment from 'moment'
+import Highcharts from 'highcharts';
+import {basicYAxis,basicXAxis} from '../utils/chartBaseConfig'
+
+//--------------------------------------
+/* 指标顺序调整  IsAxis: 0右轴 1左轴 2右2*/
+export const changeEdbOrder = (data) => {
+    // 左轴指标
+    let left_edbs = data.filter(_ => _.IsAxis===1);
+    //右轴指标
+    let right_edbs = data.filter(_ => !_.IsAxis);
+    // 右2轴指标
+    let right_two_edbs = data.filter(_ => _.IsAxis === 2);
+    // 按 左 右 右2顺序排列
+    return [left_edbs,right_edbs,right_two_edbs].flat(Infinity);
+}
+/* 拼接数据列动态name */
+export const 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
+}
+/* 预测配置 分区 */
+export const getPredictParams = ({LatestDate,MoveLatestDate,PredictChartColor,ChartStyle},chartStyle) => {
+    return {
+    zoneAxis: 'x',
+    zones: [{
+        value: new Date(MoveLatestDate||LatestDate).getTime()+1
+    }, {
+        dashStyle: 'ShortDot',
+        color: (ChartStyle==='column' || chartStyle==='column') ? 'transparent' : PredictChartColor
+    }]
+    }
+}
+// 查询范围为1年内 x轴显示为月/日 否则默认年/月
+export const 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;
+    }
+}
+//-------------------------------------
+ /* 处理轴的标识线结构 在指定轴位置上拼接标识线 
+    0:右轴 1:左轴 2:右2轴 x轴固定3
+    axisType表示x轴类型 处理时间轴的值 datetime/null 
+*/
+export const setAxisPlotLines = (axis,axisType,resData) => {
+    const { MarkersLines,ChartType } = resData.value.ChartInfo;
+    const { EdbInfoList } = resData.value;
+    if(!MarkersLines) return []
+
+    let markerLines = JSON.parse(MarkersLines);
+
+    let arr = markerLines.filter(_ => _.isShow&&_.axis===axis)
+    let plotLines = arr.map(_ => {
+        //是否是x时间轴
+        let isXDateAxis = axis===3&&axisType==='datetime';
+        let markerValue;
+        if(isXDateAxis) {
+            //季节图x轴额外拼个年份
+            let nowYear = ChartType===2 ? new Date(EdbInfoList[0].DataList[1].DataList
+                [0].DataTimestamp).getFullYear() : '';
+            markerValue = ChartType===2 
+                ? new Date(`${nowYear}-${_.value}`).getTime()
+                : new Date(_.value).getTime()
+        }else {
+            markerValue = Number(_.value)
+        }
+
+        return { 
+        value: markerValue,
+        dashStyle: _.dashStyle,
+        width: Number(_.lineWidth),
+        color: _.color,
+        label: {
+            text: _.text||'',
+            verticalAlign: _.textPosition,
+            style: {
+            color: _.textColor,
+            fontSize: _.textFontSize
+            }
+        }
+        }
+    })
+    return plotLines
+}
+/* 处理标识区拼接 axisType表示x轴类型处理时间轴的值 datetime/null */
+export const setAxisPlotAreas = (axis,axisType,resData) => {
+    const { MarkersAreas,ChartType } = resData.value.ChartInfo;
+    const { EdbInfoList } = resData.value;
+    if(!MarkersAreas) return []
+
+    let markerAreas = JSON.parse(MarkersAreas);
+
+    let arr = markerAreas.filter(_ => _.isShow&&_.axis===axis)
+    let plotBands = arr.map(_ => {
+        //是否是x时间轴
+        let isXDateAxis = axis===3&&axisType==='datetime';
+        let fromMarkerValue,toMarkerValue;
+        if(isXDateAxis) {
+            //季节图x轴额外拼个年份
+            let nowYear = ChartType===2 ? new Date(EdbInfoList[0].DataList[1].DataList
+                [0].DataTimestamp).getFullYear() : '';
+                console.log(nowYear)
+            fromMarkerValue = ChartType===2 
+                ? new Date(`${nowYear}-${_.fromValue}`).getTime()
+                : new Date(_.fromValue).getTime()
+
+            toMarkerValue = ChartType===2 
+                ? new Date(`${nowYear}-${_.toValue}`).getTime()
+                : new Date(_.toValue).getTime()
+        }else {
+            fromMarkerValue = Number(_.fromValue);
+            toMarkerValue = Number(_.toValue);
+        }
+
+        //默认label有些偏移 重新归正下
+        let positionMapValue = {
+            'top': 12,
+            'middle': 0,
+            'bottom': -10
+        }
+
+        return { 
+            from: fromMarkerValue,
+            to: toMarkerValue,
+            color: _.color,
+            label: {
+                text: _.text||'',
+                verticalAlign: _.textPosition,
+                y: positionMapValue[_.textPosition],
+                style: {
+                    color: _.textColor,
+                    fontSize: _.textFontSize
+                }
+            }
+        }
+    })
+
+    return plotBands
+}
+
+
+// 设置常规图配置 曲线
+export const setSplineOpt=(data,resData)=>{
+    console.log('setSplineOpt')
+    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)
+
+     /* 主题样式*/
+    const chartTheme =  resData.value.ChartInfo.ChartThemeStyle ? JSON.parse(resData.value.ChartInfo.ChartThemeStyle) : null;
+    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: (chartTheme&&chartTheme.lineOptions.lineType) || 'spline',
+            dashStyle: (chartTheme&&chartTheme.lineOptions.dashStyle)||'Solid',
+            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,
+                style:{
+                    ...chartTheme&&chartTheme.yAxisOptions.style
+                },
+            },
+            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,
+                style:{
+                    ...chartTheme&&chartTheme.yAxisOptions.style
+                },
+            },
+            opposite: [0,2].includes(item.IsAxis),
+            reversed: item.IsOrder,
+            min: item.MinData,
+            max: item.MaxData,
+            visible: sameSideIndex === index,
+            plotBands: setAxisPlotAreas(item.IsAxis,'',resData),
+            plotLines: setAxisPlotLines(item.IsAxis,'',resData),
+            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 isLessThanOneYear = xTimeDiffer(minTime,maxTime)
+    xAxis={
+        ...basicXAxis,
+        tickInterval:((maxTime-minTime)/6)/(24*3600*1000)>30?(maxTime-minTime)/6:24*3600*1000*30,
+        labels: {
+            formatter:  (ctx)=> {
+                return isLessThanOneYear
+                ? Highcharts.dateFormat('%m/%d', ctx.value)
+                : Highcharts.dateFormat('%y/%m', ctx.value);
+            },
+            style: {
+                ...chartTheme&&chartTheme.xAxisOptions.style
+            }
+        },
+        plotBands: setAxisPlotAreas(3,'datetime',resData),
+        plotLines: setAxisPlotLines(3,'datetime',resData)
+    }
+    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}
+    }
+}
+
+/* 图表配置 曲线*/
+export function setOptions(tableData,chartInfo) {
+    console.log('setOptions')
+    const chartData = tableData
+    //拼接标题 数据列
+    let data = [],ydata = [];
+    //y轴
+    let yItem = {
+        title: {
+            text: chartInfo.Unit||'',
+            align: 'high',
+            rotation: 0,
+            y: -12,
+            x: 0,
+            textAlign: 'left',
+            reserveSpace: false,
+        },
+        labels: {
+            formatter: function (ctx) {
+                return ctx.value;
+            },
+            align: 'center',
+        },
+        min: Number(chartInfo.MinValue),
+        max: Number(chartInfo.MaxValue),
+        lineWidth: 1,
+        lineColor: '#bfbfbf',
+        tickColor: '#bfbfbf',
+        opposite: false,
+        reversed: false,
+        visible: true,
+        gridLineWidth: 0,
+        tickWidth: 1,
+        tickLength:5,
+        tickPosition: 'inside',
+        endOnTick: false,
+        startOnTick: false,
+        showLastLabel: true, //显示最后刻度值
+        tickPixelInterval: 50
+    }
+    // 图例名称和图例文字样式
+    let dataName = chartInfo.EdbName||''
+    let color = '#333'
+    let legend = {
+        enabled: true,
+        verticalAlign: 'top',
+        margin:12,
+        itemMarginBottom:0,
+        itemMarginTop: 0,
+        itemStyle: {
+            color
+        },
+    }
+    //数据列
+    let obj = {
+        data: [],
+        type: 'spline',
+        yAxis: 0,
+        name: dataName,
+        lineWidth: 3,
+    }
+    chartData.forEach((item, index) => {
+        obj.data.push([item.DataTimestamp, item.Value]);
+    })
+    
+    data.push(obj);
+    ydata.push(yItem);
+
+    // 默认年/月
+    let xAxis = {}
+    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: function (ctx) {
+                return Highcharts.dateFormat('%y/%m', ctx.value);
+            },
+        },
+    }
+    return  {
+        series: data,
+        yAxis: ydata,
+        xAxis,
+        legend
+    }
+}

+ 1 - 1
src/views/hzyb/report/ChapterDetail.vue

@@ -281,7 +281,7 @@ export default {
          })
          if(res.code===200){
            wx.miniProgram.navigateTo({
-             url:"/pages-report/disseminatePage/disseminatePage?imgHb="+item.jump_url_mobile
+             url:"/pages-report/disseminatePage/disseminatePage?imgHb="+item.jump_url_mobile+'&title='+item.remark
            })
          }
     },

+ 1 - 1
src/views/hzyb/report/Detail.vue

@@ -306,7 +306,7 @@ export default {
             })
             if(res.code===200){
               wx.miniProgram.navigateTo({
-                url:"/pages-report/disseminatePage/disseminatePage?imgHb="+item.jump_url_mobile
+                url:"/pages-report/disseminatePage/disseminatePage?imgHb="+item.jump_url_mobile+'&title='+item.remark
               })
             }
         },

+ 78 - 0
src/views/hzyb/report/surveyDetail.vue

@@ -0,0 +1,78 @@
+<script setup>
+import { onMounted, ref } from "vue";
+import { getBannerQrcode, getBannerDetail } from "@/api/hzyb/report";
+
+import { useRoute, useRouter } from "vue-router";
+const route = useRoute();
+const router = useRouter();
+
+const codeImg = ref("");
+async function getCodeImg() {
+  const res = await getBannerQrcode({
+    UserId: route.query.userId,
+    BannerId: route.query.bannerId,
+    Remark: route.query.remark,
+  });
+  if (res.code === 200) {
+    codeImg.value = res.data;
+  }
+}
+
+const bannerImg = ref("");
+
+async function getBannerImg() {
+  const res = await getBannerDetail({
+    banner_id: route.query.bannerId,
+  });
+  if (res.code === 200) {
+    bannerImg.value = res.data.jump_url_mobile;
+  }
+}
+
+// xcx_h5/hzyb/surveyDetail?token=e235b413337f3efc4db3a0b86c78ce4bdd141bf0d0410dd4b2bab561005b41a2&userId=83664&bannerId=4&remark=炼油调研
+
+onMounted(() => {
+  console.log(route.query);
+  localStorage.setItem("hzyb-token", route.query.token);
+  getCodeImg();
+  getBannerImg();
+});
+</script>
+
+<template>
+  <div class="survey-container">
+    <img class="survey-bg-img" :src="bannerImg" />
+    <div class="r-code">
+      <img :src="codeImg" />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+* {
+  padding: 0;
+  margin: 0;
+}
+.survey-container {
+  position: relative;
+  width: 100%;
+  height: auto;
+  .survey-bg-img {
+    display: block;
+    width: 100%;
+    height: 100%;
+  }
+  .r-code {
+    position: absolute;
+    left: 50%;
+    bottom: 200px;
+    width: 256px;
+    height: 256px;
+    transform: translateX(-50%);
+    img {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

+ 139 - 0
src/views/hzyb/report/surveyHistory.vue

@@ -0,0 +1,139 @@
+<script setup>
+import { onMounted, ref } from "vue";
+import {
+  apiPublicBannerList,
+  apiPublicBannerMark,
+  bannerHistoryList,
+} from "@/api/hzyb/report";
+import { List } from "vant";
+import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router";
+const route = useRoute();
+const router = useRouter();
+const bannerDataList = ref([]);
+
+// banner 获取列表
+async function getBannerList() {
+  const res = await apiPublicBannerList();
+  if (res.code == 200) {
+    // 加载状态结束
+    bannerDataList.value = res.data;
+  }
+}
+
+const loading = ref(false);
+const finished = ref(false);
+
+function surveyHistoryLoad() {
+  if (loading.value) return;
+  page.value++;
+  getSurveyHistoryList();
+}
+const page = ref(1);
+const limit = ref(10);
+const bannerHistoryDataList = ref([]);
+
+// 获取已结束的调研
+async function getSurveyHistoryList() {
+  loading.value = true;
+  const res = await bannerHistoryList({
+    page: page.value,
+    limit: limit.value,
+  });
+  loading.value = false;
+  if (res.code === 200) {
+    let arr = res.data.list || [];
+    bannerHistoryDataList.value = [...bannerHistoryDataList.value, ...arr];
+    if (res.data.paging.is_end) {
+      finished.value = true;
+    }
+  }
+}
+
+// banner 点击事件
+async function bannerSwiperHandler(item) {
+  const res = await apiPublicBannerMark({
+    first_source: 1, //一级来源 1小程序移动 2小程序pc 3研报官网
+    second_source: 2, //二级来源 1首页 2研报详情页
+    id: item.id,
+  });
+  if (res.code === 200) {
+    wx.miniProgram.navigateTo({
+      url:
+        "/pages-report/disseminatePage/disseminatePage?imgHb=" +
+        encodeURIComponent(item.jump_url_mobile) +
+        "&title=" +
+        item.remark +
+        "&id=" +
+        item.id +
+        "&enable=" +
+        item.enable,
+    });
+  }
+}
+
+onMounted(() => {
+  localStorage.setItem("hzyb-token", route.query.token);
+  getBannerList();
+  getSurveyHistoryList();
+  document.title = "调研列表";
+});
+</script>
+
+<template>
+  <div class="container survey-history-content">
+    <div class="state-content">进行中</div>
+    <div
+      class="activity-content"
+      v-for="item in bannerDataList"
+      :key="item.id"
+      @click="bannerSwiperHandler(item)"
+    >
+      <img :src="item.image_url_mobile" alt="" />
+    </div>
+    <div class="state-content" style="margin-top: 20px">已结束</div>
+    <List
+      v-model:loading="loading"
+      :finished="finished"
+      finished-text="没有更多了"
+      @load="surveyHistoryLoad"
+    >
+      <div
+        class="activity-content"
+        v-for="item in bannerHistoryDataList"
+        :key="item.id"
+        @click="bannerSwiperHandler(item)"
+      >
+        <img :src="item.image_url_mobile" alt="" />
+      </div>
+    </List>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.survey-history-content {
+  padding: 35px;
+  .state-content {
+    position: relative;
+    font-size: 28px;
+    font-weight: 500;
+    padding-left: 15px;
+    &::after {
+      content: "";
+      position: absolute;
+      top: 8px;
+      left: 0;
+      width: 6px;
+      height: 27px;
+      background-color: #e3b377;
+    }
+  }
+  .activity-content {
+    margin-top: 20px;
+    width: 100%;
+    img {
+      width: 100%;
+      height: auto;
+    }
+  }
+}
+</style>

+ 44 - 0
src/views/hzyb/utils/chartBaseConfig.js

@@ -0,0 +1,44 @@
+//图表基础配置
+// 散点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'
+}