فهرست منبع

Merge branch 'master' of http://8.136.199.33:3000/Karsa/raiwechat_link_h5

Karsa 2 سال پیش
والد
کامیت
1f1fb9a171

+ 21 - 0
src/api/hzyb/positionAnalysis.js

@@ -0,0 +1,21 @@
+// 持仓分析模块
+
+import {get,post} from './http'
+
+/**
+ * 持仓分析详情
+ * @param data_time 查询日期,日期为空时,默认返回最新日期
+ * @param classify_name 品种名称
+ * @param classify_type 合约名称
+ * @param exchange 交易所名称
+ */
+export const apiPositionAnalysisInfo=params=>{
+    return get('/trade/analysis/top',params)
+}
+
+/**
+ * 持仓分析列表
+ */
+export const apiPositionAnalysisList=()=>{
+    return get('/trade/analysis/classify',{})
+}

+ 14 - 0
src/api/ssbg/api.js

@@ -22,4 +22,18 @@ export const researchEvents = params => {
  */
 export const myEvents = params => {
 	return get('/roadshow/my/calendar/detail', params)
+}
+
+/**
+ * 获取系统中所有用户
+ */
+export const apiSystemUsers=()=>{
+	return get('/system/role/seller/list',{})
+}
+
+/**
+ * 出差审批的日历
+ */
+export const apiBusinessTripCalendar=params=>{
+	return get('/business_trip/calendar',params)
 }

BIN
src/assets/ssbg/calendar__grey_ico.png


BIN
src/assets/ssbg/icon-add.png


BIN
src/assets/ssbg/icon-tag.png


+ 5 - 0
src/router/hzyb/index.js

@@ -37,6 +37,11 @@ export const hzybRoutes=[
                 path:"search",
                 name:'hzybChartSearch',
                 component: () => import("@/views/hzyb/chart/Search.vue")
+            },
+            {
+                path:"positionanalysis",
+                name:'hzybChartPositionAnalysis',
+                component: () => import("@/views/hzyb/chart/positionAnalysis/Index.vue")
             }
         ]
     },

+ 12 - 0
src/router/ssbg/index.js

@@ -18,4 +18,16 @@ export const ssbgRoutes = [
 			}
 		]
 	},
+	{
+		path: '/ssbg/businesstrip',
+		name:'ssbgBusinessTrip',
+		component: () => import("@/App.vue"),
+		children: [
+			{
+				path: 'calendar',
+				name: 'BusinessTripCalendar',
+				component: () => import("@/views/ssbg/businessTrip/calendar.vue")
+			}
+		]
+	}
 ]

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

@@ -429,15 +429,15 @@ const initRelevanceChart=(data)=>{
 
     let tooltip = {
         formatter: function() {
-          let str = `<p>相关性系数:${this.y}</p>`
+          let str = `<p>相关性系数:${this.y.toFixed(4)}</p><br><p>领先${this.x}期</p>`
           return str
         },
         formatterCh: function() {
-          let str = `<p>相关性系数:${this.y}</p>`
+          let str = `<p>相关性系数:${this.y.toFixed(4)}</p><br><p>领先${this.x}期</p>`
           return str
         },
         formatterEn: function() {
-          let str = `<p>Correlation coefficient:${this.y}</p>`
+          let str = `<p>Correlation coefficient:${this.y.toFixed(4)}</p><br><p>lead${this.x}stage</p>`
           return str
         }
     }

+ 404 - 0
src/views/hzyb/chart/positionAnalysis/Index.vue

@@ -0,0 +1,404 @@
+<script setup>
+import {reactive, ref,watch,nextTick} from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import ChartBox from './components/ChartBox.vue'
+import moment from 'moment'
+import { Popup, Toast,DatetimePicker,PullRefresh } from 'vant';
+import {apiPositionAnalysisInfo,apiPositionAnalysisList} from '@/api/hzyb/positionAnalysis'
+
+const route=useRoute()
+const router=useRouter()
+localStorage.setItem('hzyb-token',route.query.token)
+
+// 是否为pc端展示
+const isPcShow=ref(route.query.showType||'')
+
+// 选择时间
+let showDate=ref(false)
+const minDate=ref(new Date('2000/01/01'))
+let currentDate=ref('')//这是绑定在时间选择器的时间防止用户滑动了时间但是没点确定 这时候又把这个时间改成selectDate
+let selectDate=ref('')
+function confirmSelectDate(e){
+    // 如果选择的是 周六日 提示
+    if([0,6].includes(moment(currentDate.value).weekday())){
+        Toast('今日无数据,请选择其他日期!')
+        return
+    }
+    selectDate.value=e
+    currentDate.value=e
+    showDate.value=false
+    getInfo()
+}
+//选择时间弹窗关闭时更新 currentDate为selectDate
+function handleCloseSelectDate(){
+    // console.log('更新currentDate');
+    currentDate.value=selectDate.value
+}
+//切换 前一天\后一天 如果遇到周六日则跳过
+function handleDateChange(type){
+    let num=1 
+    if(type==='before'){
+        if(moment(selectDate.value).weekday()===1){//向前一天时 当前为周一则 跳到 上周五
+            num=3
+        }
+        currentDate.value=moment(currentDate.value).add(-num,'days')
+        selectDate.value=moment(selectDate.value).add(-num,'days')
+    }else{
+        if(moment(selectDate.value).weekday()===5){//向前一天时 当前为周五则 跳到 下周一
+            num=3
+        }
+        currentDate.value=moment(currentDate.value).add(num,'days')
+        selectDate.value=moment(selectDate.value).add(num,'days')
+    }
+    getInfo()
+}
+
+
+// 切换合约
+const allClassifyTypeList=[] //所有合约的数据
+function getAllClassifyType(){
+    apiPositionAnalysisList().then(res=>{
+        if(res.code===200){
+            const arr=res.data||[]
+            // 将数据展开
+            arr.forEach(item => {
+                item.items&&item.items.forEach(itemC1=>{
+                    itemC1.items&&itemC1.items.forEach(itemC2=>{
+                        allClassifyTypeList.push({
+                            exchange:item.exchange,
+                            classify_name:itemC1.classify_name,
+                            classify_type:itemC2.classify_type
+                        })
+                    })
+                })
+            });
+        }
+    })
+}
+getAllClassifyType()
+function handleClassifyTypeChange(type){
+    const currentExchange=route.query.exchange
+    const currentClassifyName=route.query.classify_name
+    const currentClassifyType=route.query.classify_type
+    // 找index
+    let indexNum=0
+    allClassifyTypeList.forEach((item,index)=>{
+        if(item.exchange===currentExchange&&item.classify_name===currentClassifyName&&item.classify_type===currentClassifyType) indexNum=index
+    })
+    // console.log(indexNum);
+    let obj={}
+    if(type==='before'){
+        obj=allClassifyTypeList[indexNum===0?allClassifyTypeList.length-1:indexNum-1]
+    }else{
+        obj=allClassifyTypeList[indexNum===allClassifyTypeList.length-1?0:indexNum+1]
+    }
+    // console.log(obj);
+
+    router.replace({
+        query:{
+            ...route.query,
+            exchange:obj.exchange,
+            classify_name:obj.classify_name,
+            classify_type:obj.classify_type
+        }
+    })
+    
+}
+watch(
+    ()=>route.query,
+    (n)=>{
+        selectDate.value=''
+        currentDate.value=''
+        getInfo()
+    }
+)
+
+
+// 获取详情
+let pageLoading=ref(false)
+const chartListState = reactive({
+    buy_list:{
+        name:'多单',
+        labelName:'持多单量',
+    },
+    sold_list:{
+        name:'空单',
+        labelName:'持空单量',
+    },
+    clean_buy_list:{
+        name:'净多单',
+        labelName:'净多单量',
+    },
+    clean_sold_list:{
+        name:'净空单',
+        labelName:'净空单量',
+    }
+})
+async function getInfo(){
+    pageLoading.value=true
+    const res=await apiPositionAnalysisInfo({
+        data_time:selectDate.value?moment(selectDate.value).format('YYYY-MM-DD'):'',
+        classify_name:route.query.classify_name,
+        classify_type:route.query.classify_type,
+        exchange:route.query.exchange
+    })
+    pageLoading.value=false
+    if(res.code===200){
+        const obj=res.data||{}
+        for (let key in chartListState) {
+            chartListState[key]={...chartListState[key],...obj[key]}
+        }
+        if(res.data.data_time){
+            selectDate.value=moment(res.data.data_time).toDate()
+            currentDate.value=moment(res.data.data_time).toDate()
+        }
+        document.title=`${route.query.classify_type} ${moment(selectDate.value).format('YYYYMMDD')}持仓`
+    }else{
+        // 清空数据
+        for (let key in chartListState) {
+            chartListState[key].list=null
+        }
+    }
+    const postData={
+        path:'/pages/positionAnalysis/detail',
+        params:{
+            classify_name:route.query.classify_name,
+            classify_type:route.query.classify_type,
+            exchange:route.query.exchange,
+        },
+        title:`${route.query.classify_type} ${selectDate.value?moment(selectDate.value).format('YYYY-MM-DD'):''}持仓`||'持仓分析',
+        shareImg:''
+    }
+    wx.miniProgram.postMessage({ data: postData })
+    
+    nextTick(()=>{
+        // 获取页面实际高度 传给pc端
+        const pageEl=document.getElementsByClassName('chart-position-analysis-page')
+        window.parent.postMessage({
+            opt:'updateIframeHeight',
+            height:pageEl[0].offsetHeight
+        },"*")
+
+        // 传给pc当前的数据
+        window.parent.postMessage({
+            opt:'updateInfo',
+            data_time:selectDate.value?moment(selectDate.value).format('YYYY-MM-DD'):'',
+            classify_name:route.query.classify_name,
+            classify_type:route.query.classify_type,
+            exchange:route.query.exchange,
+            title:`${route.query.classify_type} ${moment(selectDate.value).format('YYYYMMDD')}持仓`
+        },"*")
+    })
+}
+getInfo()
+
+
+// 下拉刷新
+let isRefresh=ref(false)
+function onRefresh(){
+    getInfo()
+    setTimeout(()=>{    
+        isRefresh.value=false
+    },1500)
+}
+
+
+// 返回列表
+function handleBackList(){
+    wx.miniProgram.navigateBack()
+}
+
+
+// 监听pc端传来的消息
+window.addEventListener('message',e=>{
+    // 监听选择的日期改变
+    if(e.data.opt=="date"){
+        selectDate.value=e.data.val 
+        getInfo()
+    }else if(e.data.opt==='beforeDate'){
+        handleDateChange('before')
+    }else if(e.data.opt==='nextDate'){
+        handleDateChange('next')
+    }else if(e.data.opt==='beforeClassifyType'){
+        handleClassifyTypeChange('before')
+    }else if(e.data.opt==='nextClassifyType'){
+        handleClassifyTypeChange('next')
+    }
+})
+
+
+</script>
+
+<template>
+    <PullRefresh v-model="isRefresh" @refresh="onRefresh">
+    <div :class="['chart-position-analysis-page',isPcShow?'chart-position-analysis-page-pc':'']" v-if="!pageLoading">
+        <div class="top-sticky-wrap">
+            <div class="notice-box">如无法滑动,请在左侧空白处尝试</div>
+            <div class="action-box">
+                <div 
+                    class="select-time-box"
+                    :style="{color:selectDate?'#333':'#999'}" 
+                    @click="showDate=true"
+                >{{selectDate?moment(selectDate).format('YYYY-MM-DD'):'选择日期'}}</div>
+                <span style="color:#E3B377" @click="handleDateChange('before')">前一天</span>
+                <span style="color:#E3B377" @click="handleDateChange('next')">后一天</span>
+            </div>
+        </div>
+        <div class="empty-wrap" v-if="!chartListState.buy_list.list">
+            <span>该日期无数据!</span>
+        </div>
+        <template v-else>
+        <div class="chart-wrap" v-for="(val,key) of chartListState" :key="key">
+            <div class="top-info-box">
+                <span>{{chartListState[key].name}}</span>
+                <span><span style="color:#999;margin-right:2px">总计 </span>{{chartListState[key].total_deal_value}}</span>
+                <span><span style="color:#999;margin-right:2px">较昨日 </span>{{chartListState[key].total_deal_change}}</span>
+            </div>
+            <chart-box :keyVal="key" :data="chartListState[key]"/>
+        </div>
+        </template>
+
+        <div class="bot-fixed-wrap">
+            <div class="btn black-btn" @click="handleClassifyTypeChange('before')">上个合约</div>
+            <div class="btn black-btn" @click="handleClassifyTypeChange('next')">下个合约</div>
+            <div class="btn grey-btn" @click="handleBackList">返回列表</div>
+        </div>
+
+    </div>
+    </PullRefresh>
+    <!-- 选择时间弹窗 -->
+    <Popup
+        v-model:show="showDate"
+        position="bottom"
+        round
+        @close="handleCloseSelectDate"
+    >
+        <DatetimePicker
+            v-model="currentDate"
+            type="date"
+            title=""
+            :min-date="minDate"
+            @confirm="confirmSelectDate"
+            @cancel="showDate=false"
+        />
+    </Popup>
+
+</template>
+
+<style lang="scss" scoped>
+.chart-position-analysis-page{
+    padding-bottom: calc(160px + constant(safe-area-inset-bottom));
+    padding-bottom: calc(160px + env(safe-area-inset-bottom));
+}
+.top-sticky-wrap{
+    position: sticky;
+    top: 0;
+    z-index: 99;
+    background-color: #fff;
+    .notice-box{
+        font-size: 24px;
+        background: rgba(207, 192, 159, 0.1);
+        padding: 15px 34px;
+    }
+    .action-box{
+        padding: 40px 60px 40px 34px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .select-time-box{
+            width: 360px;
+            height: 70px;
+            background: #F6F6F6;
+            border: 1px solid #E5E5E5;
+            border-radius: 35px;
+            position: relative;
+            padding-left: 80px;
+            line-height: 70px;
+            &::before{
+                content: '';
+                display: block;
+                width: 28px;
+                height: 28px;
+                background-image: url('@/assets/hzyb/chart/calendar.png');
+                background-size: cover;
+                position: absolute;
+                top: 20px;
+                left: 25px;
+            }
+        }
+    }
+}
+.bot-fixed-wrap{
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 99;
+    filter: drop-shadow(0px -2px 12px rgba(0, 0, 0, 0.08));
+    background-color: #fff;
+    padding-bottom: constant(safe-area-inset-bottom);
+    padding-bottom: env(safe-area-inset-bottom);
+    display: flex;
+    justify-content: space-between;
+    padding: 20px 34px;
+    .btn{
+        width: 30%;
+        height: 80px;
+        line-height: 80px;
+        text-align: center;
+        font-size: 28px;
+        border-radius: 40px;
+    }
+    .black-btn{
+        color: #E3B377;
+        background-color: #333333;
+    }
+    .grey-btn{
+        color: #666666;
+        background-color: #F5F5F5;
+    }
+}
+.chart-wrap{
+    width: 100%;
+    margin-bottom: 60px;
+    .top-info-box{
+        padding: 0 34px 30px 34px;
+        text-align: center;
+        span{
+            display: inline-block;
+            margin-right: 50px;
+        }
+    }
+}
+.empty-wrap{
+    text-align: center;
+    padding-top: 300px;
+}
+
+// @media (min-width: 600px){
+    .chart-position-analysis-page-pc{
+        padding: 0;
+        .top-sticky-wrap{
+            display: none;
+        }
+        .bot-fixed-wrap{
+            display: none;
+        }
+        .empty-wrap{
+            font-size: 16PX;
+        }
+        .chart-wrap{
+            margin-bottom: 60PX;
+            .top-info-box{
+                padding: 0 20PX 20PX;
+                text-align: center;
+                font-size: 20PX;
+                span{
+                    display: inline-block;
+                    margin-right: 30PX;
+                }
+            }
+        }
+    }
+// }
+</style>

+ 335 - 0
src/views/hzyb/chart/positionAnalysis/components/ChartBox.vue

@@ -0,0 +1,335 @@
+<script setup>
+import {ref,onMounted,watch } from 'vue'
+import { Popup } from 'vant';
+import Highcharts from 'highcharts';
+import HighchartsMore from 'highcharts/highcharts-more'
+import HighchartszhCN  from '../../../utils/highcahrts-zh_CN'
+import { useRoute } from 'vue-router';
+HighchartszhCN(Highcharts)
+HighchartsMore(Highcharts)
+
+const route=useRoute()
+
+// 是否为pc端展示
+const isPcShow=ref(route.query.showType||'')
+
+const props=defineProps({
+    keyVal:String,
+    data:Object
+})
+
+//配色表
+const colorMap=new Map([
+    ['buy_list',['#FFD600','#F32F2F','#52D424']],
+    ['sold_list',['#C6DDFF','#363EFF','#52D424']],
+    ['clean_buy_list',['#FFD600','#F32F2F','#52D424']],
+    ['clean_sold_list',['#C6DDFF','#363EFF','#52D424']],
+])
+//图表默认配置项
+const chartDefaultOpts={
+    chart: {
+		type: 'column',
+        inverted:true,//xy轴换位置
+	},
+    title: {
+		text:''
+	},
+    //版权信息
+	credits: {enabled:false},
+    plotOptions:{
+        column: {
+            pointPadding: 0,
+			stacking: 'normal',
+            dataLabels: {
+                enabled: true,
+                align: 'right',
+                color: '#333',
+                x:60,
+                formatter:function(e){
+                    return this.point.options.isLabel
+                }
+            },
+            events: {
+                click: function (event) {
+                    const name=event.point.category
+                    showDetail(name)
+                }
+            }
+        },
+    },
+    legend: {
+		enabled: false,//关闭图例
+	},
+    tooltip: {
+        enabled: false
+    },
+
+}
+
+
+//绘图
+function chartRender(){
+    let options={}
+    let categories=[]
+    let series=[
+        {
+            name: '总数',
+            data: [],
+        },
+        {
+            name: '增长',
+            data: [],
+        },
+        {
+            name:'减少',
+            data:[]
+        },
+    ]
+    let totalArr=[],increaseArr=[],reduceArr=[];
+    props.data.list.forEach((item,index)=>{
+        categories.push(item.deal_short_name)
+
+        // 处理series
+        if(item.deal_change<0){//减少
+            totalArr.push({y:item.deal_value})
+            increaseArr.push({y:0})
+            reduceArr.push({y:-item.deal_change,isLabel:`${item.deal_value}/${item.deal_change}`})
+        }else{
+            totalArr.push({y:item.deal_value-item.deal_change})
+            reduceArr.push({y:0})
+            increaseArr.push({y:item.deal_change,isLabel:`${item.deal_value}/+${item.deal_change}`})
+        }
+        
+    })
+    series[0].data=totalArr
+    series[1].data=increaseArr
+    series[2].data=reduceArr
+    let xAxis={
+        categories: categories,
+        tickWidth:1,
+        tickPosition:'outside',
+    }
+    let yAxis={
+        opposite:true,
+        gridLineWidth: 1,
+        gridLineColor:'#E4E4E4',
+        gridLineDashStyle: 'longdash',
+        endOnTick: false,
+        showLastLabel: true,
+        lineWidth: 1,
+        tickWidth:1,
+        tickPosition:'outside',
+        title: {
+          text: '',
+        },
+        reversedStacks: false
+    }
+    
+    options.colors=colorMap.get(props.keyVal)
+    options.xAxis=xAxis
+    options.yAxis=yAxis
+    options.series=series
+    options={...chartDefaultOpts,...options}
+    // console.log('渲染',options);
+    Highcharts.chart(`chart-box-${props.keyVal}`,options)
+}
+
+
+//点击柱子显示弹窗
+let showDetailPop=ref(false)
+let showDetailData=ref(null)
+function showDetail(name){
+    let data={}
+    props.data.list.forEach(item=>{
+        if(item.deal_short_name===name){
+            data=item
+        }
+    })
+    // 判断是否是在pc中 如果是则通知pc显示弹窗
+    // console.log(window.innerWidth);
+    if(isPcShow.value=='pc'){
+        window.parent.postMessage({
+            opt:'showDetail',
+            data:JSON.stringify({...data,type_name:props.data.name})
+        },"*")
+        return
+    }
+    showDetailData.value=data
+    showDetailPop.value=true
+}
+
+
+onMounted(()=>{
+    watch(
+        ()=>props.data,
+        (n)=>{
+            if(n.list){
+                chartRender()
+            }
+        },
+        {
+            deep:true,
+            immediate:true
+        }
+    )
+})
+
+
+</script>
+
+<template>
+    <div :class="['chart-render-box',isPcShow?'chart-render-box-pc':'']">
+        <div class="label-box">
+            <div>
+                <span class="color-box" :style="{background:colorMap.get(keyVal)[0]}"></span>
+                <span>{{data.labelName}}</span>
+            </div>
+            <div>
+                <span class="color-box" :style="{background:colorMap.get(keyVal)[1]}"></span>
+                <span>增</span>
+            </div>
+            <div>
+                <span class="color-box" :style="{background:colorMap.get(keyVal)[2]}"></span>
+                <span>减</span>
+            </div>
+        </div>
+        <div class="chart-content">
+			<img class="mark-img" src="../../../../../assets/hzyb/chart/mark.png" alt="">
+			<div class="chart-box" :id="'chart-box-'+keyVal"></div>
+		</div>
+        
+    </div>
+    <!-- 详情弹窗 -->
+    <Popup 
+        v-model:show="showDetailPop"
+        round
+    >
+        <div class="mobile-detail-pop-wrap">
+            <h2 class="title">{{showDetailData.deal_short_name}}</h2>
+            <div class="table-box">
+                <div class="item">
+                    <div>持{{data.name}}</div>
+                    <div>{{showDetailData.deal_value}}</div>
+                </div>
+                <div class="item">
+                    <div>增减</div>
+                    <div>{{showDetailData.deal_change}}</div>
+                </div>
+                <div class="item">
+                    <div>占比</div>
+                    <div>{{(showDetailData.rate*100).toFixed(2)}}%</div>
+                </div>
+                <div class="item">
+                    <div>前{{showDetailData.rank}}名持净多单</div>
+                    <div>{{showDetailData.before_all_value}}</div>
+                </div>
+                <div class="item">
+                    <div>增减</div>
+                    <div>{{showDetailData.before_all_change}}</div>
+                </div>
+                <div class="item">
+                    <div>占比</div>
+                    <div>{{(showDetailData.before_all_rate*100).toFixed(2)}}%</div>
+                </div>
+            </div>
+            <div class="close-btn" @click="showDetailPop=false">关闭</div>
+        </div>
+    </Popup>
+</template>
+
+<style lang="scss" scoped>
+.chart-render-box{
+    width: 100%;
+    overflow: hidden;
+    .label-box{
+        display: flex;
+        justify-content: center;
+        div{
+            margin-right: 30px;
+        }
+        .color-box{
+            display: inline-block;
+            width: 22px;
+            height: 22px;
+            margin-right: 10px;
+            border-radius: 4px;
+        }
+    }
+    .chart-content{
+        width: 100%;
+        height: 70vh;
+        position: relative;
+		.mark-img{
+			position: absolute;
+			width: 58%;
+			left: 50%;
+			top: 50%;
+			transform: translate(-50%,-50%);
+			pointer-events: none;
+			z-index: 2;
+		}
+        .chart-box{
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+.mobile-detail-pop-wrap{
+    width: 90vw;
+    .title{
+        text-align: center;
+        font-size: 32px;
+        padding: 30px;
+    }
+    .table-box{
+        display: flex;
+        flex-wrap: wrap;
+        .item{
+            width: 33.33%;
+            text-align: center;
+            padding: 30px 20px;
+            border-right: 1px solid #E5E5E5;
+            border-bottom: 1px solid #E5E5E5;
+            div:first-child{
+                color: #999999;
+                font-size: 24px;
+            }
+            div:last-child{
+                font-size: 32px;
+                font-weight: 600;
+            }
+        }
+        
+
+    }
+    .close-btn{
+        text-align: center;
+        line-height: 96px;
+        color: #E3B377;
+    }
+}
+// @media (min-width: 600px){
+    .chart-render-box-pc{
+        .label-box{
+            display: flex;
+            justify-content: center;
+            font-size: 14PX;
+            div{
+                margin-right: 20PX;
+            }
+            .color-box{
+                display: inline-block;
+                width: 14PX;
+                height: 14PX;
+                margin-right: 8PX;
+                border-radius: 4PX;
+                position: relative;
+                top: 2PX;
+            }
+        }
+        .chart-content{
+            height: 800PX;
+        }
+    }
+// }
+</style>

+ 321 - 0
src/views/ssbg/businessTrip/calendar.vue

@@ -0,0 +1,321 @@
+<script setup>
+import {ref} from 'vue'
+import { Sticky,Popup,Cascader  } from 'vant';
+import swiperCalendar from '../components/swiperCalendar.vue';
+import {apiSystemUsers,apiBusinessTripCalendar} from '@/api/ssbg/api'
+import moment from 'moment';
+import 'moment/dist/locale/zh-cn';
+import { useRoute } from 'vue-router';
+moment.locale('zh-cn');
+
+const route=useRoute()
+localStorage.setItem('ssbg-token',route.query.token);
+document.title = '出差日历';
+
+const IsBusinessTrip=ref(route.query.IsBusinessTrip||false)//是否为审批人
+
+const swiperCalendarRef=ref(null)
+
+// 选择用户
+const userFieldNames=ref({
+    text: 'RealName',
+    value: 'AdminId',
+    children: 'ChildrenList',
+})
+let selectUserShow=ref(false)
+let selectUserValue=ref('')
+let lastSelectVal=''
+let userOpts=ref([])
+function onFinish(e){
+
+    if(lastSelectVal!=selectUserValue.value){
+        lastSelectVal=e.value
+    }else{//点击的上次选中的人 则重置掉
+        selectUserValue.value=''
+        lastSelectVal=''
+    }
+    getInfo()
+    selectUserShow.value=false
+}
+
+// 获取系统中所有用户’
+function getSystemUsers(){
+    apiSystemUsers().then(res=>{
+        if(res.code==200){
+            userOpts.value=res.data.List||[]
+        }
+    })
+}
+getSystemUsers()
+
+// 添加申请
+function goAddApply(){
+    wx.miniProgram.navigateTo({
+        url: `/pages-approve/businessTrip/add`,
+    });
+}
+
+// 切换到今天
+function goToDay(){
+    swiperCalendarRef.value.getRecentWeek(moment().format('YYYY-MM-DD'))
+    swiperCalendarRef.value.selectDateHandle(moment().format('YYYY-MM-DD'),1)
+}
+
+//选择的日期改变
+let selectDateVal=ref(moment().format('YYYY-MM-DD'))
+function datechangeHandle(e){
+    selectDateVal.value=e
+    getInfo()
+}
+
+// 表单数据
+let listData=ref([])
+let loading=ref(false)
+function getInfo(){
+    loading.value=true
+    listData.value=[]
+    apiBusinessTripCalendar({
+        TripDate:selectDateVal.value,
+        AdminId:selectUserValue.value
+    }).then(res=>{
+        loading.value=false
+        if(res.code===200){
+            const arr=res.data||[]
+            let temArr=[]
+            arr.forEach(item => {
+                temArr.push({
+                    ...item,
+                    tableName:item.ApplyRealName
+                })
+                if(item.PeerPeopleList){
+                    item.PeerPeopleList.forEach(_item=>{
+                        temArr.push({
+                            ...item,
+                            tableName:_item.PeerPeopleName,
+                            GroupName:_item.PeerGroupName
+                        })
+                    })
+                }
+            });
+            listData.value=temArr
+        }
+    })
+}
+getInfo()
+
+// 显示详情
+let showDetail=ref(false)
+let detailInfo=ref(null)
+function handleShowDetail(item){
+    if(item.Status=='待审批'){
+        wx.miniProgram.navigateTo({
+            url: `/pages-approve/businessTrip/detail?id=`+item.BusinessApplyId,
+        });
+        return
+    }
+    detailInfo.value=item
+    showDetail.value=true
+}
+
+</script>
+
+<template>
+    <div class="business-trip-page">
+        <Sticky>
+            <div class="header">
+                <swiperCalendar @dateChange="datechangeHandle" ref="swiperCalendarRef"/>
+            </div>
+        </Sticky>
+        <div class="top-date-box">{{moment(selectDateVal).format('MM/DD')}}({{moment(selectDateVal).format('ddd')}})</div>
+        <div class="empty-box" v-if="!loading&&listData.length==0">暂无数据</div>
+        <div class="table-box" v-else>
+            <div class="item" v-for="item in listData" :key="item.BusinessApplyId" @click="handleShowDetail(item)">
+                <div class="left">{{item.tableName}}({{item.GroupName}})</div>
+                <div :class="['right',item.Status=='待审批'?'yellow-text':'']">{{item.Province}}{{item.City}}({{item.DayTotal}}天)</div>
+            </div>
+        </div>
+        <div class="fix-bot-action">
+            <div class="action-box">
+                <div class="item" @click="goAddApply" v-if="IsBusinessTrip!='true'">
+                    <img src="@/assets/ssbg/icon-add.png" alt="">
+                    <div>添加申请</div>
+                </div>
+                <div class="item" @click="goToDay">
+                    <img src="@/assets/ssbg/calendar_ico.png" alt="">
+                    <div>今天</div>
+                </div>
+                <div class="item" @click="selectUserShow=true">
+                    <img src="@/assets/ssbg/choose_ico.png" alt="">
+                    <div>选择用户</div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- 选择用户弹窗 -->
+    <Popup 
+        v-model:show="selectUserShow" 
+        round 
+        position="bottom"
+    >
+        <Cascader
+            v-model="selectUserValue"
+            title="请选择用户"
+            :options="userOpts"
+            :field-names="userFieldNames"
+            active-color="#3385FF"
+            @close="selectUserShow = false"
+            @finish="onFinish"
+        />
+    </Popup>
+
+    <!-- 详情 -->
+    <Popup 
+        v-model:show="showDetail" 
+        round 
+        position="center"
+    >
+        <div class="detail-wrap">
+            <div class="content">
+                <h2 class="title">出差详情</h2>
+                <div>{{moment(detailInfo.ArriveDate).format('MM.DD (ddd)')}}--{{moment(detailInfo.ReturnDate).format('MM.DD (ddd)')}}</div>
+                <div class="item-info">
+                    <span>申请人:</span>
+                    <span>{{detailInfo.ApplyRealName}}</span>
+                </div>
+                <div class="item-info">
+                    <span>目的地:</span>
+                    <span>{{detailInfo.Province}}{{detailInfo.City}}</span>
+                </div>
+                <div class="item-info">
+                    <span>出差事由:</span>
+                    <span>{{detailInfo.Reason}}</span>
+                </div>
+                <div class="item-info">
+                    <span>交通工具:</span>
+                    <span>{{detailInfo.Transportation}}</span>
+                </div>
+                <div class="item-info" v-if="detailInfo.PeerPeopleName">
+                    <span>同行人:</span>
+                    <span>{{detailInfo.PeerPeopleName}}</span>
+                </div>
+            </div>
+            <div class="btn" @click="showDetail=false">知道了</div>
+        </div>
+    </Popup>
+</template>
+
+<style lang="scss" scoped>
+.business-trip-page{
+    min-height: 100vh;
+    background-color: #f5f5f5;
+    padding-bottom: calc(150px + constant(safe-area-inset-bottom));
+    padding-bottom: calc(150px + env(safe-area-inset-bottom));
+    .header{
+        padding-top: 20px;
+        padding-bottom: 20px;
+        background-color: #fff;
+    }
+    .top-date-box{
+        text-align: center;
+        border-top: 1px solid #f5f5f5;
+        font-size: 32px;
+        padding: 20px 0;
+        background-color: #fff;
+    }
+    .table-box{
+        background-color: #fff;
+        margin-top: 10px;
+        .item{
+            display: flex;
+            border-bottom: 1px solid #f5f5f5;
+            font-size: 32px;
+            .left{
+                flex: 1;
+                border-right: 1px solid #f5f5f5;
+                padding: 34px 20px;
+            }
+            .right{
+                flex: 2;
+                padding: 34px 20px;
+            }
+            .yellow-text{
+                color: #FF8A00;
+                position: relative;
+                &::after{
+                    content: '';
+                    display: block;
+                    width: 80px;
+                    height: 80px;
+                    background-image: url('@/assets/ssbg/icon-tag.png');
+                    background-size: cover;
+                    position: absolute;
+                    top: 0;
+                    right: 0;
+                }
+            }
+        }
+    }
+
+    .empty-box{
+        text-align: center;
+        padding-top: 150px;
+    }
+
+    .fix-bot-action{
+        position: fixed;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        z-index: 99;
+        background-color: #fff;
+        box-shadow: 0px -3px 6px rgba(172, 172, 172, 0.16);
+        padding-top: 15px;
+        padding-bottom: calc(15px + constant(safe-area-inset-bottom));
+        padding-bottom: calc(15px + env(safe-area-inset-bottom));
+        .action-box{
+            display: flex;
+            align-items: center;
+            justify-content: space-around;
+            color: #3385FF;
+            img{
+                width: 40px;
+                display: block;
+                margin: 0 auto;
+            }
+        }
+    }
+
+}
+.detail-wrap{
+    width: 85vw;
+    .content{
+        padding: 30px;
+        font-size: 32px;
+        .title{
+            font-size: 34px;
+            text-align: center;
+        }
+        .item-info{
+            margin-top: 10px;
+            display: flex;
+            span:first-child{
+                width: 150px;
+                flex-shrink: 0;
+                margin-right: 10px;
+                text-align: right;
+            }
+            span:last-child{
+                flex: 1;
+            }
+        }
+    }
+    .btn{
+        height: 82px;
+        border-top: 1px solid #F0F0F0;
+        color: #3385FF;
+        font-size: 32px;
+        text-align: center;
+        line-height: 80px;
+    }
+}
+</style>

+ 32 - 2
src/views/ssbg/components/swiperCalendar.vue

@@ -1,5 +1,6 @@
 <script setup>
 import { reactive, toRefs, ref, watch, onMounted } from 'vue';
+import { DatetimePicker , Popup } from 'vant';
 import moment from 'moment';
 import 'moment/dist/locale/zh-cn';
 moment.locale('zh-cn');
@@ -151,6 +152,15 @@ const touchEndHandle = (e) => {
   }
 };
 
+// 唤起日历
+let showCalendar=ref(false)
+function onSelectCalendarConfirm(e){
+  const t=moment(e).format('YYYY-MM-DD')
+  getRecentWeek(t)
+  selectDateHandle(t,1)
+  showCalendar.value=false
+}
+
 /* 选中日期 更新日历 */
 watch(
   () => state.selectDate,
@@ -171,9 +181,12 @@ const { weekDateList, selectDate, moveIndex, touch } = toRefs(state);
 </script>
 
 <template>
-  <h3 class="current-date">{{ moment(selectDate).format('YYYY年MM月') }}</h3>
+  <h3 class="current-date">
+    <img class="icon" @click="showCalendar=true" src="@/assets/ssbg/calendar__grey_ico.png" alt="" v-if="$route.path==='/ssbg/businesstrip/calendar'">
+    <span>{{ moment(selectDate).format('YYYY年MM月') }}</span>
+  </h3>
   <ul class="weekend-ul">
-    <li v-for="item in weekDays">
+    <li v-for="item in weekDays" :key="item.label">
       <span>{{ item.label }}</span>
     </li>
   </ul>
@@ -215,12 +228,29 @@ const { weekDateList, selectDate, moveIndex, touch } = toRefs(state);
       </ul>
     </div>
   </div>
+
+  <Popup 
+        v-model:show="showCalendar" 
+        round 
+        position="bottom"
+  >
+    <DatetimePicker
+      type="date"
+      title="选择年月日"
+      @confirm="onSelectCalendarConfirm"
+      @cancel="showCalendar=false"
+    />
+  </Popup>
 </template>
 
 <style scoped lang="scss">
 .current-date {
   text-align: center;
   margin-bottom: 10px;
+  .icon{
+    width: 34px;
+    margin-right: 29px;
+  }
 }
 .weekend-ul {
   display: flex;