Initial commit

This commit is contained in:
南宁网宿科技
2023-06-03 23:57:39 +08:00
commit 13bed2bafb
1017 changed files with 175796 additions and 0 deletions
@@ -0,0 +1,69 @@
<template>
<a-card :bordered="false" title="浏览器分布" :body-style="{ padding: '0px' }">
<v-chart
ref="browserChartRef"
:option="browserChartOption"
style="height: 222px"
/>
</a-card>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { message } from 'ant-design-vue/es';
import { use } from 'echarts/core';
import type { EChartsCoreOption } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { PieChart } from 'echarts/charts';
import { TooltipComponent, LegendComponent } from 'echarts/components';
import VChart from 'vue-echarts';
import { getBrowserCountList } from '@/api/dashboard/monitor';
import useEcharts from '@/utils/use-echarts';
use([CanvasRenderer, PieChart, TooltipComponent, LegendComponent]);
//
const browserChartRef = ref<InstanceType<typeof VChart> | null>(null);
useEcharts([browserChartRef]);
// 浏览器分布饼图配置
const browserChartOption: EChartsCoreOption = reactive({});
/* 获取用户浏览器分布数据 */
const getBrowserCountData = () => {
getBrowserCountList()
.then((data) => {
Object.assign(browserChartOption, {
tooltip: {
trigger: 'item',
confine: true,
borderWidth: 1
},
legend: {
bottom: 5,
itemWidth: 10,
itemHeight: 10,
icon: 'circle',
data: data.map((d) => d.name)
},
series: [
{
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '43%'],
label: {
show: false
},
data: data
}
]
});
})
.catch((e) => {
message.error(e.message);
});
};
getBrowserCountData();
</script>
@@ -0,0 +1,147 @@
<template>
<a-card :bordered="false" title="用户分布">
<a-row>
<a-col v-bind="styleResponsive ? { sm: 18, xs: 24 } : { span: 18 }">
<v-chart
ref="userCountMapChartRef"
:option="userCountMapOption"
style="height: 469px"
/>
</a-col>
<a-col v-bind="styleResponsive ? { sm: 6, xs: 24 } : { span: 6 }">
<div
v-for="item in userCountDataRank"
:key="item.name"
class="monitor-user-count-item ele-cell"
>
<div>{{ item.name }}</div>
<div class="ele-cell-content">
<a-progress
status="normal"
:show-info="false"
:percent="item.percent"
/>
</div>
<div>{{ item.value }}</div>
</div>
</a-col>
</a-row>
</a-card>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { message } from 'ant-design-vue/es';
import { use, registerMap } from 'echarts/core';
import type { EChartsCoreOption } from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
import { MapChart } from 'echarts/charts';
import {
VisualMapComponent,
GeoComponent,
TooltipComponent
} from 'echarts/components';
import VChart from 'vue-echarts';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
import { getChinaMapData, getUserCountList } from '@/api/dashboard/monitor';
import type { UserCount } from '@/api/dashboard/monitor/model';
import useEcharts from '@/utils/use-echarts';
use([
CanvasRenderer,
MapChart,
VisualMapComponent,
GeoComponent,
TooltipComponent
]);
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
//
const userCountMapChartRef = ref<InstanceType<typeof VChart> | null>(null);
useEcharts([userCountMapChartRef]);
// 用户分布前 10 名
const userCountDataRank = ref<UserCount[]>([]);
// 用户分布地图配置
const userCountMapOption: EChartsCoreOption = reactive({});
/* 获取中国地图数据并注册地图 */
const registerChinaMap = () => {
getChinaMapData()
.then((data) => {
registerMap('china', data);
getUserCountData();
})
.catch((e) => {
message.error(e.message);
});
};
/* 获取用户分布数据 */
const getUserCountData = () => {
getUserCountList()
.then((data) => {
const temp = data.sort((a, b) => b.value - a.value);
const min = temp[temp.length - 1].value || 0;
const max = temp[0].value || 1;
//
const list = temp.length > 10 ? temp.slice(0, 15) : temp;
userCountDataRank.value = list.map((d) => {
return {
name: d.name,
value: d.value,
percent: (d.value / max) * 100
};
});
//
Object.assign(userCountMapOption, {
tooltip: {
trigger: 'item',
borderWidth: 1
},
visualMap: {
min: min,
max: max,
text: ['高', '低'],
calculable: true
},
series: [
{
name: '用户数',
label: {
show: true
},
type: 'map',
map: 'china',
data: data
}
]
});
})
.catch((e) => {
message.error(e.message);
});
};
registerChinaMap();
</script>
<style lang="less" scoped>
.monitor-user-count-item {
margin-bottom: 8px;
:deep(.ant-progress-inner) {
background: none;
}
.ele-cell-content {
padding-right: 10px;
}
}
</style>
@@ -0,0 +1,70 @@
<template>
<a-card :bordered="false" title="在线人数">
<div class="monitor-online-num-card">
<div>{{ currentTime }}</div>
<div class="monitor-online-num-title">
<ele-count-up :end-val="onlineNum" />
</div>
<div class="monitor-online-num-text">在线总人数</div>
<a-badge status="processing" :text="updateTimeText" />
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref, computed, onBeforeUnmount } from 'vue';
import { toDateString } from 'ele-admin-pro/es';
// 在线人数更新定时器
let onlineNumTimer: number | null = null;
// 在线总人数倒计时
const updateTime = ref(10);
// 当前时间
const currentTime = ref(toDateString(new Date(), 'HH:mm:ss'));
// 在线人数
const onlineNum = ref(228);
// 在线人数倒计时文字
const updateTimeText = computed(() => updateTime.value + ' 秒后更新');
/* 在线人数更新倒计时 */
const startUpdateOnlineNum = () => {
onlineNumTimer = window.setInterval(() => {
currentTime.value = toDateString(new Date(), 'HH:mm:ss');
if (updateTime.value === 1) {
updateTime.value = 10;
onlineNum.value = 100 + Math.round(Math.random() * 900);
} else {
updateTime.value--;
}
}, 1000);
};
onBeforeUnmount(() => {
// 销毁定时器
if (onlineNumTimer) {
clearInterval(onlineNumTimer);
onlineNumTimer = null;
}
});
startUpdateOnlineNum();
</script>
<style lang="less" scoped>
.monitor-online-num-card {
text-align: center;
}
.monitor-online-num-title {
line-height: 1;
font-size: 50px;
margin: 22px 0 14px;
}
.monitor-online-num-text {
margin-bottom: 22px;
}
</style>
@@ -0,0 +1,166 @@
<!-- 统计卡片 -->
<template>
<a-row :gutter="16">
<a-col
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
>
<a-card :bordered="false" class="monitor-count-card">
<ele-tag color="blue" shape="circle" size="large">
<eye-filled />
</ele-tag>
<h1 class="monitor-count-card-num">21.2 k</h1>
<div class="monitor-count-card-text">总访问人数</div>
<ele-avatar-list :data="visitUsers" size="small" :max="4" />
</a-card>
</a-col>
<a-col
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
>
<a-card :bordered="false" class="monitor-count-card">
<ele-tag color="orange" shape="circle" size="large">
<fire-filled />
</ele-tag>
<h1 class="monitor-count-card-num">1.6 k</h1>
<div class="monitor-count-card-text">点击量(近30天)</div>
<div class="monitor-count-card-trend ele-text-success">
<up-outlined />
<span>110.5%</span>
</div>
<a-tooltip title="指标说明">
<question-circle-outlined class="monitor-count-card-tips" />
</a-tooltip>
</a-card>
</a-col>
<a-col
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
>
<a-card :bordered="false" class="monitor-count-card">
<ele-tag color="red" shape="circle" size="large">
<flag-filled />
</ele-tag>
<h1 class="monitor-count-card-num">826.0</h1>
<div class="monitor-count-card-text">到达量(近30天)</div>
<div class="monitor-count-card-trend ele-text-danger">
<down-outlined />
<span>15.5%</span>
</div>
</a-card>
</a-col>
<a-col
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
>
<a-card :bordered="false" class="monitor-count-card">
<ele-tag color="green" shape="circle" size="large">
<thunderbolt-filled />
</ele-tag>
<h1 class="monitor-count-card-num">28.8 %</h1>
<div class="monitor-count-card-text">转化率(近30天)</div>
<div class="monitor-count-card-trend ele-text-success">
<up-outlined />
<span>65.8%</span>
</div>
<a-tooltip title="指标说明">
<question-circle-outlined class="monitor-count-card-tips" />
</a-tooltip>
</a-card>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import {
QuestionCircleOutlined,
EyeFilled,
FireFilled,
FlagFilled,
ThunderboltFilled,
UpOutlined,
DownOutlined
} from '@ant-design/icons-vue';
import { storeToRefs } from 'pinia';
import { useThemeStore } from '@/store/modules/theme';
interface VisitUserType {
key: string | number;
name: string;
avatar: string;
}
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
// 访问人数
const visitUsers = ref<VisitUserType[]>([
{
key: 1,
name: 'SunSmile',
avatar:
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
},
{
key: 2,
name: '你的名字很好听',
avatar:
'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg'
},
{
key: 3,
name: '全村人的希望',
avatar:
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
},
{
key: 4,
name: 'Jasmine',
avatar:
'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg'
},
{
key: 5,
name: '酷酷的大叔',
avatar:
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
},
{
key: 6,
name: '管理员',
avatar: 'https://cdn.eleadmin.com/20200610/avatar.jpg'
}
]);
</script>
<style lang="less" scoped>
.monitor-count-card {
text-align: center;
.monitor-count-card-num {
margin-top: 6px;
font-size: 32px;
}
.monitor-count-card-text {
font-size: 12px;
margin: 8px 0;
opacity: 0.8;
}
.monitor-count-card-trend {
font-weight: bold;
line-height: 26px;
& > .anticon {
margin-right: 6px;
}
}
.monitor-count-card-tips {
position: absolute;
top: 16px;
right: 16px;
cursor: pointer;
opacity: 0.6;
}
}
</style>
@@ -0,0 +1,75 @@
<template>
<a-card
:bordered="false"
title="用户活跃度"
:body-style="{ padding: '56px 0' }"
>
<div class="ele-cell">
<div class="ele-cell-content ele-text-center">
<div class="monitor-progress-group">
<a-progress
type="circle"
:percent="70"
stroke-color="#52c41a"
:show-info="false"
:width="161"
/>
<a-progress
type="circle"
:percent="60"
stroke-color="#1890ff"
:show-info="false"
:width="121"
:stroke-width="5"
/>
<a-progress
type="circle"
:percent="35"
stroke-color="#f5222d"
:show-info="false"
:width="91"
:stroke-width="4"
/>
</div>
</div>
<div class="monitor-progress-legends">
<div class="ele-text-small ele-elip">
<a-badge color="green" text="活跃率: 70%" />
</div>
<div class="ele-text-small ele-elip">
<a-badge color="blue" text="留存率: 60%" />
</div>
<div class="ele-text-small ele-elip">
<a-badge color="red" text="跳出率: 35%" />
</div>
</div>
</div>
</a-card>
</template>
<style lang="less" scoped>
.monitor-progress-group {
position: relative;
display: inline-block;
.ant-progress:not(:first-child) {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin-top: -1px;
}
}
.monitor-progress-legends {
padding-right: 24px;
:deep(.ant-badge-status-text) {
font-size: 12px;
}
& > div + div {
margin-top: 8px;
}
}
</style>
@@ -0,0 +1,86 @@
<template>
<a-card :bordered="false" title="用户评价">
<div class="ele-cell ele-cell-align-bottom">
<div style="font-size: 51px; line-height: 1">4.5</div>
<div class="ele-cell-content">
<a-rate :value="userRate" disabled />
<span style="color: #fadb14; margin-left: 8px">很棒</span>
</div>
</div>
<div class="ele-cell" style="margin: 18px 0">
<div style="font-size: 28px; line-height: 1" class="ele-text-placeholder">
-0%
</div>
<div class="ele-cell-content ele-text-small ele-text-secondary">
当前没有评价波动
</div>
</div>
<div class="ele-cell">
<div class="ele-cell-content">
<a-progress :percent="60" stroke-color="#52c41a" :show-info="false" />
</div>
<div class="monitor-evaluate-text">
<star-filled />
<span>5 : 368 人</span>
</div>
</div>
<div class="ele-cell">
<div class="ele-cell-content">
<a-progress :percent="40" stroke-color="#1890ff" :show-info="false" />
</div>
<div class="monitor-evaluate-text">
<star-filled />
<span>4 : 256 人</span>
</div>
</div>
<div class="ele-cell">
<div class="ele-cell-content">
<a-progress :percent="20" stroke-color="#faad14" :show-info="false" />
</div>
<div class="monitor-evaluate-text">
<star-filled />
<span>3 : 49 人</span>
</div>
</div>
<div class="ele-cell">
<div class="ele-cell-content">
<a-progress :percent="10" stroke-color="#f5222d" :show-info="false" />
</div>
<div class="monitor-evaluate-text">
<star-filled />
<span>2 : 14 人</span>
</div>
</div>
<div class="ele-cell">
<div class="ele-cell-content">
<a-progress :percent="0" :show-info="false" />
</div>
<div class="monitor-evaluate-text">
<star-filled />
<span>1 : 0 人</span>
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { StarFilled } from '@ant-design/icons-vue';
// 用户评分
const userRate = ref(4.5);
</script>
<style lang="less" scoped>
.monitor-evaluate-text {
width: 90px;
flex-shrink: 0;
white-space: nowrap;
opacity: 0.8;
& > .anticon {
font-size: 12px;
margin: 0 6px 0 8px;
}
}
</style>
@@ -0,0 +1,79 @@
<template>
<a-card :bordered="false" title="用户满意度">
<div class="ele-cell ele-text-center">
<div class="ele-cell-content" style="font-size: 24px">856</div>
<div class="ele-cell-content">
<div class="monitor-face-smile"><i></i></div>
<div class="ele-text-secondary ele-elip" style="margin-top: 8px">
正面评论
</div>
</div>
<h2 class="ele-cell-content ele-text-success">82%</h2>
</div>
<a-divider style="margin: 26px 0" />
<div class="ele-cell ele-text-center">
<div class="ele-cell-content" style="font-size: 24px">60</div>
<div class="ele-cell-content">
<div class="monitor-face-cry"><i></i></div>
<div class="ele-text-secondary ele-elip" style="margin-top: 8px">
负面评论
</div>
</div>
<h2 class="ele-cell-content ele-text-danger">9%</h2>
</div>
</a-card>
</template>
<style lang="less" scoped>
.monitor-face-smile,
.monitor-face-cry {
width: 50px;
height: 50px;
display: inline-block;
background: #fbd971;
border-radius: 50%;
position: relative;
}
.monitor-face-smile > i,
.monitor-face-smile:before,
.monitor-face-smile:after,
.monitor-face-cry > i,
.monitor-face-cry:before,
.monitor-face-cry:after {
width: 28px;
height: 28px;
border-radius: 50%;
transform: rotate(225deg);
border: 3px solid #f0c419;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
position: absolute;
bottom: 8px;
left: 11px;
}
.monitor-face-smile:before,
.monitor-face-smile:after,
.monitor-face-cry:before,
.monitor-face-cry:after {
content: '';
width: 12px;
height: 12px;
left: 8px;
top: 14px;
border-color: #f29c1f;
transform: rotate(45deg);
}
.monitor-face-smile:after,
.monitor-face-cry:after {
left: auto;
right: 8px;
}
.monitor-face-cry > i {
transform: rotate(45deg);
bottom: -6px;
}
</style>