|
@@ -0,0 +1,434 @@
|
|
|
|
+<script setup>
|
|
|
|
+import dayjs from 'dayjs';
|
|
|
|
+import autofit from 'autofit.js'
|
|
|
|
+import * as echarts from 'echarts';
|
|
|
|
+import useUserStore from '@/store/modules/user';
|
|
|
|
+import { dashboardApi } from '@/api/voice/dashboard';
|
|
|
|
+import { getEchartLineOption, getEchart3dOption, getEchartBarOption } from './echartConfig';
|
|
|
|
+
|
|
|
|
+const userStore = useUserStore();
|
|
|
|
+
|
|
|
|
+const screenData = ref({});
|
|
|
|
+
|
|
|
|
+const dateInfo = ref({
|
|
|
|
+ weekDay: '',
|
|
|
|
+ today: '',
|
|
|
|
+ time: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+let timer = null;
|
|
|
|
+let myLineChart = null;
|
|
|
|
+let my3dChart = null;
|
|
|
|
+let myBarChart = null;
|
|
|
|
+
|
|
|
|
+const echartLineRef = ref(null);
|
|
|
|
+const echart3dRef = ref(null);
|
|
|
|
+const echartBarRef = ref(null);
|
|
|
|
+
|
|
|
|
+const updateTime = () => {
|
|
|
|
+ const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
|
|
|
|
+ const todayWeekDay = dayjs().day();
|
|
|
|
+ dateInfo.value = {
|
|
|
|
+ weekDay: '星期' + weekDays[todayWeekDay],
|
|
|
|
+ today: dayjs().format('YYYY-MM-DD'),
|
|
|
|
+ time: dayjs().format('HH:mm:ss')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const formatSeconds = (seconds, num) => {
|
|
|
|
+ // 将秒转换为分钟
|
|
|
|
+ let minutes = seconds / num;
|
|
|
|
+
|
|
|
|
+ // 使用 toLocaleString 方法格式化数字,保留两位小数
|
|
|
|
+ // 并添加千位分隔符
|
|
|
|
+ let formattedMinutes = minutes.toLocaleString('en-US', {
|
|
|
|
+ minimumFractionDigits: 2,
|
|
|
|
+ maximumFractionDigits: 2
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return formattedMinutes;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const formatSecondsToTime = (seconds) => {
|
|
|
|
+ // 计算完整的分钟数和剩余的秒数
|
|
|
|
+ let minutes = Math.floor(seconds / 60);
|
|
|
|
+ let remainingSeconds = seconds % 60;
|
|
|
|
+
|
|
|
|
+ // 如果有超过60分钟的部分,计算小时
|
|
|
|
+ let hours = Math.floor(minutes / 60);
|
|
|
|
+ minutes = minutes % 60;
|
|
|
|
+
|
|
|
|
+ // 确保分钟和秒数都是两位数,不足两位前面补零
|
|
|
|
+ let formattedMinutes = String(minutes).padStart(2, '0');
|
|
|
|
+ let formattedSeconds = String(remainingSeconds).padStart(2, '0');
|
|
|
|
+
|
|
|
|
+ // 如果有小时,则包含在输出中
|
|
|
|
+ if (hours > 0) {
|
|
|
|
+ let formattedHours = String(hours).padStart(2, '0');
|
|
|
|
+ return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
|
|
|
+ } else {
|
|
|
|
+ return `${formattedMinutes}:${formattedSeconds}`;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const initEchart = async() => {
|
|
|
|
+ const { data } = await dashboardApi.getHomeScreenCount()
|
|
|
|
+ const {
|
|
|
|
+ recent7DayAndCounts, recent7DayAndCountsLastYear,
|
|
|
|
+ recent7MonthAndCounts, recent7MonthAndCountsLastYear,
|
|
|
|
+ businessTop7
|
|
|
|
+ } = data;
|
|
|
|
+
|
|
|
|
+ screenData.value = {
|
|
|
|
+ ...data,
|
|
|
|
+ inTimes: formatSeconds(data.inTimes, 60),
|
|
|
|
+ inTimesAvg: formatSecondsToTime(data.inTimesAvg),
|
|
|
|
+ totalTimes: formatSeconds(data.totalTimes, 3600),
|
|
|
|
+ robotInTimes: formatSeconds(data.robotInTimes, 60),
|
|
|
|
+ robotTimesAvg: formatSecondsToTime(data.robotTimesAvg),
|
|
|
|
+ humanTimesAvg: formatSecondsToTime(data.humanTimesAvg),
|
|
|
|
+ humanInTimes: formatSeconds(data.humanInTimes, 60)
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 近7日电话呼入量 - 折线图
|
|
|
|
+ const lineSeriesData = [
|
|
|
|
+ { name: '近七日', data:recent7DayAndCounts },
|
|
|
|
+ { name: '同比', data:recent7DayAndCountsLastYear }
|
|
|
|
+ ];
|
|
|
|
+ const lineXAxisData = recent7DayAndCounts.map(item => dayjs(item.date).format('MM.DD'))
|
|
|
|
+
|
|
|
|
+ // 近七日呼入电话量趋势
|
|
|
|
+ const barXAxisData = recent7MonthAndCounts.map(item => dayjs(item.date).format('MM-DD'))
|
|
|
|
+ const seriesData = recent7MonthAndCounts.map(item => item.count)
|
|
|
|
+ const seriesData1 = recent7MonthAndCountsLastYear.map(item => item.count)
|
|
|
|
+
|
|
|
|
+ myLineChart = echarts.init(echartLineRef.value);
|
|
|
|
+ my3dChart = echarts.init(echart3dRef.value);
|
|
|
|
+ myBarChart = echarts.init(echartBarRef.value);
|
|
|
|
+
|
|
|
|
+ myLineChart.setOption(getEchartLineOption({
|
|
|
|
+ xAxisData: lineXAxisData,
|
|
|
|
+ seriesData: lineSeriesData
|
|
|
|
+ }));
|
|
|
|
+
|
|
|
|
+ my3dChart.setOption(getEchart3dOption(businessTop7));
|
|
|
|
+
|
|
|
|
+ myBarChart.setOption(getEchartBarOption({
|
|
|
|
+ barXAxisData,
|
|
|
|
+ seriesData,
|
|
|
|
+ seriesData1
|
|
|
|
+ }));
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const windowResize = () => {
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ myLineChart.resize();
|
|
|
|
+ // my3dChart.resize();
|
|
|
|
+ // myBarChart.resize();
|
|
|
|
+ }, 300)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ autofit.init({
|
|
|
|
+ dw: 1920,
|
|
|
|
+ dh: 1080,
|
|
|
|
+ el:"#screen-view",
|
|
|
|
+ resize: true,
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ timer = setInterval(updateTime, 1000);
|
|
|
|
+
|
|
|
|
+ updateTime();
|
|
|
|
+
|
|
|
|
+ initEchart();
|
|
|
|
+
|
|
|
|
+ window.addEventListener('resize', windowResize, false);
|
|
|
|
+
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ clearInterval(timer);
|
|
|
|
+})
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<template>
|
|
|
|
+ <div class="dashboard-viewport">
|
|
|
|
+ <div id="screen-view">
|
|
|
|
+ <div class="header">
|
|
|
|
+ <div class="date space-x-[24px]">
|
|
|
|
+ <div class="time">{{ dateInfo.time }}</div>
|
|
|
|
+ <ul class="week">
|
|
|
|
+ <li>{{dateInfo.weekDay}}</li>
|
|
|
|
+ <li class="num">{{ dateInfo.today }}</li>
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="line"></div>
|
|
|
|
+ <div class="user space-x-[10px]">
|
|
|
|
+ <img :src="userStore.avatar" alt="" class="avatar">
|
|
|
|
+ <span>{{ userStore.nickName }}</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="main space-y-[20px]">
|
|
|
|
+ <div class="top">
|
|
|
|
+ <div class="count-list">
|
|
|
|
+ <div class="count-item"><p class="num">{{ screenData.inTotal }}</p><p>近7天呼入总量</p></div>
|
|
|
|
+ <div class="count-item"><p class="num">{{ screenData.successRate }}%</p><p>近7天接通率</p></div>
|
|
|
|
+ <div class="count-item"><p class="num">{{ screenData.inTimes }}<span class="text-[14px] text-[#fff] opacity-70">分钟</span></p><p>近7天呼入时长</p></div>
|
|
|
|
+ <div class="count-item"><p class="num">{{ screenData.inTimesAvg }}</p><p>近7天平均呼入时长</p></div>
|
|
|
|
+ <div class="count-item"><p class="num">{{ screenData.totalTimes }}<span class="text-[14px] text-[#fff] opacity-70">小时</span></p><p>累计呼入时长</p></div>
|
|
|
|
+ <div class="count-item"><p class="num">{{ screenData.totalCounts }}</p><p>累计呼入总量</p></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="middle">
|
|
|
|
+ <div class="echart-list">
|
|
|
|
+ <div class="echart-item">
|
|
|
|
+ <div class="content" ref="echartLineRef"></div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="echart-item">
|
|
|
|
+ <div class="content echart-content" ref="echart3dRef"></div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="echart-item">
|
|
|
|
+ <div class="content" ref="echartBarRef"></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="bottom">
|
|
|
|
+ <div class="left">
|
|
|
|
+ <ul class="statistic-list">
|
|
|
|
+ <li class="statistic-item"><p class="title">今日呼入总量</p><p class="num">{{ screenData.robotInTotal }}</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">通话时长</p><p class="num space-x-[4px]"><span>{{ screenData.robotInTimes }}</span><span class="unit">分钟</span></p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">平均通话时长</p><p class="num">{{ screenData.robotTimesAvg }}</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">转人工总量</p><p class="num">{{ screenData.transfer2Human }}</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">当前排队总数</p><p class="num">300</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">分流比例</p><p class="num">{{ screenData.robotStreamRate }}%</p></li>
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="right">
|
|
|
|
+ <ul class="statistic-list">
|
|
|
|
+ <li class="statistic-item"><p class="title">今日呼入总量</p><p class="num">{{ screenData.humanInTotal }}</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">通话时长</p><p class="num space-x-[4px]"><span>{{ screenData.humanInTimes }}</span><span class="unit">分钟</span></p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">平均通话时长</p><p class="num">{{ screenData.humanTimesAvg }}</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">置闲人数</p><p class="num">{{ screenData.onlineTotal }}</p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">当前排队总数</p><p class="num"></p></li>
|
|
|
|
+ <li class="statistic-item"><p class="title">分流比例</p><p class="num">{{ screenData.humanStreamRate }}%</p></li>
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+.dashboard-viewport {
|
|
|
|
+ width: 100vw;
|
|
|
|
+ height: 100vh;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-main.png') no-repeat;
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
+
|
|
|
|
+ #screen-view {
|
|
|
|
+ height: 100%;
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-flow: column;
|
|
|
|
+ padding-bottom: 30px;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.header {
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
+ height: 110px;
|
|
|
|
+ padding: 26px 26px 46px 26px;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-title.png') no-repeat;
|
|
|
|
+ background-size: cover;
|
|
|
|
+ color: #fff;
|
|
|
|
+
|
|
|
|
+ .date {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ color: #BFDFFF;
|
|
|
|
+ .time {
|
|
|
|
+ width: 120px;
|
|
|
|
+ text-shadow: 0px 1px 3px rgba(5, 12, 25, 0.54);
|
|
|
|
+ font-family: D-DIN-PRO;
|
|
|
|
+ font-size: 36px;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ }
|
|
|
|
+ .week {
|
|
|
|
+ text-align: center;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ .num {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ font-family: D-DIN-PRO;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .line {
|
|
|
|
+ width: 2px;
|
|
|
|
+ height: 30px;
|
|
|
|
+ margin: 0 38px;
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ background: rgba(205, 233, 251, 0.32);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .user {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ color: #FFF;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ .avatar {
|
|
|
|
+ width: 32px;
|
|
|
|
+ height: 32px;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ background: #fff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.main {
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ padding: 30px 50px 18px 50px;
|
|
|
|
+ .top {
|
|
|
|
+ height: 120px;
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ .count-list {
|
|
|
|
+ display: grid;
|
|
|
|
+ gap: 12px;
|
|
|
|
+ grid-template-columns: repeat(6, minmax(260px, 1fr));
|
|
|
|
+ height: 100%;
|
|
|
|
+ .count-item:nth-child(1) {
|
|
|
|
+ background: url("@/assets/images/dashboard/bg-count-01.png") no-repeat;
|
|
|
|
+ background-size: contain;
|
|
|
|
+ }
|
|
|
|
+ .count-item:nth-child(2) {
|
|
|
|
+ background: url("@/assets/images/dashboard/bg-count-02.png") no-repeat;
|
|
|
|
+ background-size: contain;
|
|
|
|
+ }
|
|
|
|
+ .count-item:nth-child(3) {
|
|
|
|
+ background: url("@/assets/images/dashboard/bg-count-03.png") no-repeat;
|
|
|
|
+ background-size: contain;
|
|
|
|
+ }
|
|
|
|
+ .count-item:nth-child(4) {
|
|
|
|
+ background: url("@/assets/images/dashboard/bg-count-04.png") no-repeat;
|
|
|
|
+ background-size: contain;
|
|
|
|
+ }
|
|
|
|
+ .count-item:nth-child(5) {
|
|
|
|
+ background: url("@/assets/images/dashboard/bg-count-05.png") no-repeat;
|
|
|
|
+ background-size: contain;
|
|
|
|
+ }
|
|
|
|
+ .count-item:nth-child(6) {
|
|
|
|
+ background: url("@/assets/images/dashboard/bg-count-06.png") no-repeat;
|
|
|
|
+ background-size: contain;
|
|
|
|
+ }
|
|
|
|
+ .count-item {
|
|
|
|
+ padding: 20px 0 0 22px;
|
|
|
|
+ color: #FFF;
|
|
|
|
+ .num {
|
|
|
|
+ font-family: D-DIN-PRO;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ font-size: 34px;
|
|
|
|
+ }
|
|
|
|
+ .text {
|
|
|
|
+ color: #E6FFF5;
|
|
|
|
+ font-size: 16px;
|
|
|
|
+ font-weight: 400px;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ .middle {
|
|
|
|
+ height: 346px;
|
|
|
|
+ .echart-list {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ // display: grid;
|
|
|
|
+ // grid-template-columns: repeat(3, 1fr);
|
|
|
|
+ // gap: 12px;
|
|
|
|
+ height: 100%;
|
|
|
|
+ .echart-item:nth-child(1) {
|
|
|
|
+ margin-right: 12px;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-middle-title-01.png') center top no-repeat, url('@/assets/images/dashboard/bg-middle.png') center center no-repeat;
|
|
|
|
+ background-size: contain, 100% 100%;
|
|
|
|
+ }
|
|
|
|
+ .echart-item:nth-child(2) {
|
|
|
|
+ margin-right: 12px;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-middle-title-02.png') center top no-repeat, url('@/assets/images/dashboard/bg-middle.png') center center no-repeat;
|
|
|
|
+ background-size: contain, 100% 100%;
|
|
|
|
+ }
|
|
|
|
+ .echart-item:nth-child(3) {
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-middle-title-03.png') center top no-repeat, url('@/assets/images/dashboard/bg-middle.png') center center no-repeat;
|
|
|
|
+ background-size: contain, 100% 100%;
|
|
|
|
+ }
|
|
|
|
+ .echart-item {
|
|
|
|
+ width: 33%;
|
|
|
|
+ height: 100%;
|
|
|
|
+ padding-top: 44px;
|
|
|
|
+ }
|
|
|
|
+ .content {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 100%;
|
|
|
|
+ }
|
|
|
|
+ .echart-content {
|
|
|
|
+ background: url('https://static.fuxicarbon.com/bigModel/pc/bg-3d-echart.png') left center no-repeat;
|
|
|
|
+ background-size: 60% 50%;
|
|
|
|
+ background-position: 0px 80%;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ .bottom {
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ display: grid;
|
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
|
+ gap: 12px;
|
|
|
|
+ height: 350px;
|
|
|
|
+ .left {
|
|
|
|
+ padding-top: 44px;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-middle-title-04.png') left top no-repeat, url('@/assets/images/dashboard/bg-bottom.png') left center no-repeat;
|
|
|
|
+ background-size: 598px 44px, 100% 100%;
|
|
|
|
+ }
|
|
|
|
+ .right {
|
|
|
|
+ padding-top: 44px;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-middle-title-05.png') left top no-repeat, url('@/assets/images/dashboard/bg-bottom.png') left center no-repeat;
|
|
|
|
+ background-size: 598px 44px, 100% 100%;
|
|
|
|
+ }
|
|
|
|
+ .statistic-list {
|
|
|
|
+ display: grid;
|
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
|
+ grid-template-rows: repeat(2, 1fr);
|
|
|
|
+ gap: 15px;
|
|
|
|
+ height: 100%;
|
|
|
|
+ padding: 30px 42px;
|
|
|
|
+ .statistic-item {
|
|
|
|
+ height: 100%;
|
|
|
|
+ padding: 20px 0 0 36px;
|
|
|
|
+ background: url('@/assets/images/dashboard/bg-bottom-item.png') center center no-repeat;
|
|
|
|
+ background-size: 100% 100%;
|
|
|
|
+ .title {
|
|
|
|
+ color: #E6FFF5;
|
|
|
|
+ font-size: 18px;
|
|
|
|
+ line-height: 26px;
|
|
|
|
+ letter-spacing: 2.25px;
|
|
|
|
+ }
|
|
|
|
+ .num {
|
|
|
|
+ color: #FFF;
|
|
|
|
+ text-shadow: 0px 0px 3.75px rgba(58, 206, 237, 0.50);
|
|
|
|
+ font-family: D-DIN-PRO;
|
|
|
|
+ font-size: 36px;
|
|
|
|
+ font-weight: bold;
|
|
|
|
+ }
|
|
|
|
+ .unit {
|
|
|
|
+ color: #9c9c9c;
|
|
|
|
+ font-size: 18px;
|
|
|
|
+ font-weight: 400;
|
|
|
|
+ letter-spacing: 2.25px;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</style>
|