123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- <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';
- import NumberAnimation from '@/components/NumberAnimation';
- import usePermissionStore from '@/store/modules/permission';
- const userStore = useUserStore();
- const permissionStore = usePermissionStore();
- const screenData = ref({});
- const dateInfo = ref({
- weekDay: '',
- today: '',
- time: ''
- })
- let timer = null;
- let dataTimer = 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;
- }
- // 将秒转换为小时、分钟和秒 00:12 d的函数
- 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,
- ai7DayAndCounts
- } = data;
- screenData.value = {
- ...data,
- todayInTimesAvg: formatSecondsToTime(data.todayInTimesAvg),
- // 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'))
-
- // 近七日Ai处理情况
- const barXAxisData = ai7DayAndCounts.map(item => dayjs(item.date).format('MM.DD'))
- const seriesData = ai7DayAndCounts.map(item => item.aiCounts)
- const seriesData1 = ai7DayAndCounts.map(item => item.totalCounts)
- myLineChart.setOption(getEchartLineOption({
- xAxisData: lineXAxisData,
- seriesData: lineSeriesData
- }));
- // 本月业务类型 TOP7
- my3dChart.setOption(getEchart3dOption(businessTop7));
- myBarChart.setOption(getEchartBarOption({
- barXAxisData,
- seriesData,
- seriesData1
- }));
- }
- const windowResize = () => {
- setTimeout(() => {
- myLineChart.resize();
- my3dChart.resize();
- myBarChart.resize();
- }, 300)
- }
- const hasRouterAuth = () => {
- return permissionStore.routes.some(route => {
- return route.path === '/voice/dashboard'
- })
- }
- onMounted(() => {
- autofit.init({
- dw: 1920,
- dh: 1080,
- el:"#screen-view",
- resize: true,
- })
- myLineChart = echarts.init(echartLineRef.value);
- my3dChart = echarts.init(echart3dRef.value);
- myBarChart = echarts.init(echartBarRef.value);
- timer = setInterval(updateTime, 1000);
- dataTimer = setInterval(initEchart, 5 * 60 * 1000);
-
- updateTime();
- initEchart();
- window.addEventListener('resize', windowResize, false);
- });
- onUnmounted(() => {
- clearInterval(timer);
- clearInterval(dataTimer);
- })
- </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">
- <div class="top">
- <div class="count-list">
- <div class="count-item">
- <p class="num"><NumberAnimation :to="screenData.todayInTotal"></NumberAnimation></p>
- <p>今日呼入总量</p>
- </div>
- <div class="count-item">
- <p class="num"><NumberAnimation :to="screenData.todayAiTotal"></NumberAnimation></p>
- <p>今日AI处理量<span class="text-[#00FCFF]">「占比{{screenData.todayAiRate || 0}}%」</span></p>
- </div>
- <div class="count-item">
- <p class="num">{{ screenData.todayInTimesAvg || "00:00" }}</p>
- <p>今日平均呼入时长</p>
- </div>
- <div class="count-item">
- <p class="num"><NumberAnimation :to="screenData.totalCounts"></NumberAnimation></p>
- <p style="display: flex; align-items: center;">
- <span style="margin-right: 5px;">累计呼入总量</span>
- <el-tooltip
- effect="dark"
- content="开始时间: 2025年01月03日"
- placement="top"
- >
- <el-icon><Warning /></el-icon>
- </el-tooltip>
- </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">
- <div class="title-status">当前在线人工坐席:3人 机器人坐席:10个</div>
- <ul class="statistic-list">
- <li class="statistic-item">
- <p class="title">AI处理量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.todayAiTotal"></NumberAnimation></p>
- <span class="ratio green">占比 {{screenData.todayAiRate || 0}}%</span>
- </div>
- </li>
- <li class="statistic-item">
- <p class="title">人工处理量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.todayHumanTotal"></NumberAnimation></p>
- <span class="ratio green">占比 {{screenData.todayHumanRate || 0}}%</span>
- </div>
- </li>
- <li class="statistic-item">
- <p class="title">电话接通量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.todaySuccessCounts"></NumberAnimation></p>
- <span class="ratio green">占比 {{screenData.todaySuccessRate || 0}}%</span>
- </div>
- </li>
- <li class="statistic-item">
- <p class="title">今日呼入总量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.todayInTotal"></NumberAnimation></p>
- </div>
- </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">AI处理量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.yesterdayAiTotal"></NumberAnimation></p>
- <span class="ratio red">占比 {{screenData.yesterdayAiRate || 0}}%</span>
- </div>
- </li>
- <li class="statistic-item">
- <p class="title">人工处理量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.yesterdayHumanTotal"></NumberAnimation></p>
- <span class="ratio red">占比 {{screenData.yesterdayHumanRate || 0}}%</span>
- </div>
- </li>
- <li class="statistic-item">
- <p class="title">电话接通量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.yesterdaySuccessCounts"></NumberAnimation></p>
- <span class="ratio red">占比 {{screenData.yesterdaySuccessRate || 0}}%</span>
- </div>
- </li>
- <li class="statistic-item">
- <p class="title">昨日呼入总量</p>
- <div class="numeric-stats-box">
- <p class="num"><NumberAnimation :to="screenData.yesterdayInTotal"></NumberAnimation></p>
- </div>
- </li>
- <!-- <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 {
- flex: 1;
- display: flex;
- flex-flow: column;
- padding: 20px 50px 18px 50px;
- .top {
- height: 120px;
- flex-shrink: 0;
- .count-list {
- display: grid;
- gap: 12px;
- grid-template-columns: repeat(4, minmax(260px, 1fr));
- height: 100%;
- .count-item:nth-child(1) {
- background: url("@/assets/images/dashboard/bg-count-01.png") no-repeat;
- background-size: 100% 100%;
- }
- .count-item:nth-child(2) {
- background: url("@/assets/images/dashboard/bg-count-02.png") no-repeat;
- background-size: 100% 100%;
- }
- .count-item:nth-child(3) {
- background: url("@/assets/images/dashboard/bg-count-03.png") no-repeat;
- background-size: 100% 100%;
- }
- .count-item:nth-child(4) {
- background: url("@/assets/images/dashboard/bg-count-04.png") no-repeat;
- background-size: 100% 100%;
- }
- .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: 10px 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: 350px;
- margin-top: 36px;
- .echart-list {
- display: flex;
- align-items: center;
- 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;
- margin-top: 24px;
- .left {
- position: relative;
- 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: 100% 44px, 100% 100%;
- .title-status {
- position: absolute;
- top: 0;
- right: 0;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- width: 100%;
- height: 44px;
- padding-right: 30px;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.8);
- }
- }
- .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: 100% 44px, 100% 100%;
- }
- .statistic-list {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- grid-template-rows: repeat(2, 1fr);
- gap: 15px;
- height: 100%;
- padding: 30px 42px;
- .statistic-item {
- height: 100%;
- padding: 20px 36px 0 36px;
- background: url('@/assets/images/dashboard/bg-bottom-item.png') center center no-repeat;
- background-size: 100% 100%;
- overflow: hidden;
- .numeric-stats-box {
- display: flex;
- justify-content: space-between;
- align-items: flex-end;
- height: 50px;
- .ratio {
- font-size: 14px;
- line-height: 24px;
- }
- .green {
- color: #00F040;
- }
- .red {
- color: #FF7300
- }
- }
- .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;
- line-height: 50px;
- }
- .unit {
- color: #9c9c9c;
- font-size: 18px;
- font-weight: 400;
- letter-spacing: 2.25px;
- }
- }
- }
- }
- }
- </style>
|