feat: 新增首页

This commit is contained in:
wanglihui 2023-08-01 01:58:53 +08:00
parent 2ab7a579ce
commit 2cf8c7e791
9 changed files with 513 additions and 12 deletions

View File

@ -15,6 +15,8 @@
"@icon-park/vue-next": "^1.4.2",
"@vueuse/core": "^10.1.2",
"axios": "^1.4.0",
"dayjs": "^1.11.9",
"echarts": "^5.4.3",
"element-plus": "^2.3.7",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",

23
pnpm-lock.yaml generated
View File

@ -17,6 +17,12 @@ dependencies:
axios:
specifier: ^1.4.0
version: 1.4.0
dayjs:
specifier: ^1.11.9
version: 1.11.9
echarts:
specifier: ^5.4.3
version: 5.4.3
element-plus:
specifier: ^2.3.7
version: 2.3.7(vue@3.3.4)
@ -2606,6 +2612,13 @@ packages:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
dev: true
/echarts@5.4.3:
resolution: {integrity: sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==}
dependencies:
tslib: 2.3.0
zrender: 5.4.4
dev: false
/electron-to-chromium@1.4.452:
resolution: {integrity: sha512-ITLyB1brjWat2oEIzbPjewgN6DnJlmW8isz4pMC54FctnaKhkZR1s9cCVgRZzrk7i1kW1n0k2G4hs3ibwFalyw==}
dev: true
@ -4978,6 +4991,10 @@ packages:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
/tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
dev: false
/tslib@2.6.0:
resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==}
dev: true
@ -5689,3 +5706,9 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true
/zrender@5.4.4:
resolution: {integrity: sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==}
dependencies:
tslib: 2.3.0
dev: false

26
src/api/statistic.ts Normal file
View File

@ -0,0 +1,26 @@
import request from '@/utils/axios';
// 统计
export function statisticsCountnumGet(params: any) {
return request({
url: '/statistics/countnum',
method: 'get',
params
});
}
// 用户注册统计
export function statisticsRegisteruserGet(start: string, end: string) {
return request({
url: `/statistics/registeruser/${start}/${end}`,
method: 'get'
});
}
// 新建群统计
export function statisticsCreatedgroupGet(start: string, end: string) {
return request({
url: `/statistics/createdgroup/${start}/${end}`,
method: 'get'
});
}

27
src/hooks/useEcharts.ts Normal file
View File

@ -0,0 +1,27 @@
import { onDeactivated, onBeforeUnmount } from 'vue';
import * as echarts from 'echarts';
/**
* @description 使 Echarts ()
* @param {Element} myChart Echarts实例 ()
* @param {Object} options Echarts的参数 ()
* */
export const useEcharts = (myChart: echarts.ECharts, options: echarts.EChartsCoreOption) => {
if (options && typeof options === 'object') {
myChart.setOption(options);
}
const echartsResize = () => {
myChart && myChart.resize();
};
window.addEventListener('resize', echartsResize);
// 防止 echarts 页面 keepAlive 时,还在继续监听页面
onDeactivated(() => {
window.removeEventListener('resize', echartsResize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', echartsResize);
});
};

View File

@ -0,0 +1,166 @@
<template>
<div class="el-card flex flex-col box-border h-full w-full relative">
<div class="h-50px pl-12px pr-12px box-border flex items-center justify-between bd-title">
<div class="bd-title-left">
<p class="m-0 font-600">新建群数量统计</p>
</div>
</div>
<div class="flex-1 flex overflow-hidden p-12px">
<div class="flex-1">
<div ref="echartsRef" class="w-full h-full"></div>
</div>
<div class="w-400px">
<div class="mb-12px font-600">新建群排名</div>
<div class="panking">
<template v-for="(item, index) in sortData" :key="index">
<div v-if="index < 10" class="panking-item">
<div class="index">{{ index + 1 }}</div>
<div class="date flex-1">{{ item.time }}</div>
<div class="num">{{ item.value }}</div>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" name="AddGroup" setup>
import * as echarts from 'echarts';
import dayjs from 'dayjs';
// API
import { statisticsCreatedgroupGet } from '@/api/statistic';
interface Iprops {
title?: string;
value?: number;
icon?: string;
}
interface ISort {
time: string;
value: number;
}
defineProps<Iprops>();
let myChart: echarts.ECharts | null = null;
const echartsRef = ref<HTMLElement>();
const sortData = ref<ISort[]>([]);
const dates = ref<string[]>([]);
const option: any = reactive({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: [],
axisLabel: {
color: '#a1a1a1'
}
},
yAxis: [
{
type: 'value',
axisLabel: {
color: '#a1a1a1'
}
}
],
series: {
name: 'Direct',
type: 'bar',
emphasis: {
focus: 'series'
},
data: []
}
});
const getStatisticsCreatedgroup = () => {
statisticsCreatedgroupGet(dates.value[0], dates.value[1]).then((res: any) => {
const sortList: ISort[] = [];
const xAxisData = [];
const seriesData = [];
for (const key in res) {
sortList.push({
time: key,
value: res[key]
});
xAxisData.push(key);
seriesData.push(res[key]);
}
sortList.sort((a, b) => {
return b.value - a.value;
});
sortData.value = sortList;
option.xAxis.data = xAxisData;
option.series.data = seriesData;
myChart && myChart.setOption(option);
myChart && myChart.resize();
window.addEventListener('resize', echartsResize);
});
};
const echartsResize = () => {
myChart && myChart.resize();
};
onMounted(() => {
const start = dayjs(new Date().getTime() - 30 * 24 * 60 * 60 * 1000).format('YYYY-MM-DD');
const end = dayjs().format('YYYY-MM-DD');
dates.value = [start, end];
myChart = echarts.init(echartsRef.value as HTMLElement);
getStatisticsCreatedgroup();
});
// echarts keepAlive
onDeactivated(() => {
window.removeEventListener('resize', echartsResize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', echartsResize);
});
</script>
<style lang="scss" scoped>
//
.bd-title {
border-bottom: 1px solid var(--el-card-border-color);
}
.panking {
.panking-item {
display: flex;
justify-content: center;
margin-bottom: 16px;
:last-child {
margin-bottom: 0;
}
.index {
width: 20px;
height: 20px;
line-height: 20px;
font-weight: 600;
font-size: 12px;
text-align: center;
background-color: rgb(0, 80, 179);
color: rgb(255, 255, 255);
border-radius: 20px;
margin-right: 12px;
}
}
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<div class="el-card flex flex-col box-border h-full w-full relative">
<div class="h-50px pl-12px pr-12px box-border flex items-center justify-between bd-title">
<div class="bd-title-left">
<p class="m-0 font-600">注册用户统计</p>
</div>
</div>
<div class="flex-1 flex overflow-hidden p-12px">
<div class="flex-1">
<div ref="echartsRef" class="w-full h-full"></div>
</div>
<div class="w-400px h-full">
<div class="mb-12px font-600">注册量排名</div>
<div class="panking">
<template v-for="(item, index) in sortData" :key="index">
<div v-if="index < 10" class="panking-item">
<div class="index">{{ index + 1 }}</div>
<div class="date flex-1">{{ item.time }}</div>
<div class="num">{{ item.value }}</div>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" name="AddUser" setup>
import * as echarts from 'echarts';
import dayjs from 'dayjs';
// API
import { statisticsRegisteruserGet } from '@/api/statistic';
interface IProps {
title?: string;
value?: number;
icon?: string;
}
interface ISort {
time: string;
value: number;
}
defineProps<IProps>();
let myChart: echarts.ECharts | null = null;
const echartsRef = ref<HTMLElement>();
const sortData = ref<ISort[]>([]);
const dates = ref<string[]>([]);
const option: any = reactive({
xAxis: {
type: 'category',
data: []
},
tooltip: {
trigger: 'axis'
},
yAxis: {
type: 'value'
},
series: {
data: [],
type: 'line'
}
});
const getStatisticsRegisteruser = () => {
statisticsRegisteruserGet(dates.value[0], dates.value[1]).then((res: any) => {
const sortList: ISort[] = [];
const xAxisData = [];
const seriesData = [];
for (const key in res) {
sortList.push({
time: key,
value: res[key]
});
xAxisData.push(key);
seriesData.push(res[key]);
}
sortList.sort((a, b) => {
return b.value - a.value;
});
sortData.value = sortList;
option.xAxis.data = xAxisData;
option.series.data = seriesData;
myChart && myChart.setOption(option);
myChart && myChart.resize();
window.addEventListener('resize', echartsResize);
});
};
const echartsResize = () => {
myChart && myChart.resize();
};
onMounted(() => {
const start = dayjs(new Date().getTime() - 30 * 24 * 60 * 60 * 1000).format('YYYY-MM-DD');
const end = dayjs().format('YYYY-MM-DD');
dates.value = [start, end];
myChart = echarts.init(echartsRef.value as HTMLElement);
getStatisticsRegisteruser();
});
// echarts keepAlive
onDeactivated(() => {
window.removeEventListener('resize', echartsResize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', echartsResize);
});
</script>
<style lang="scss" scoped>
//
.bd-title {
border-bottom: 1px solid var(--el-card-border-color);
}
.panking {
.panking-item {
display: flex;
justify-content: center;
margin-bottom: 16px;
:last-child {
margin-bottom: 0;
}
.index {
width: 20px;
height: 20px;
line-height: 20px;
font-weight: 600;
font-size: 12px;
text-align: center;
background-color: rgb(0, 80, 179);
color: rgb(255, 255, 255);
border-radius: 20px;
margin-right: 12px;
}
}
}
</style>

View File

@ -1,8 +1,39 @@
<template>
<div class="el-card box-border p-12px h-full w-full">
<div>我的</div>
<div>100</div>
<div class="el-card box-border p-12px h-full w-full relative">
<div>{{ title }}</div>
<p class="num">{{ value }}</p>
<div v-if="icon" class="right-icon">
<component :is="icon" size="35" />
</div>
</div>
</template>
<script lang="ts" name="Statistics" setup></script>
<script lang="ts" name="Statistics" setup>
interface Iprops {
title?: string;
value?: number;
icon?: string;
}
defineProps<Iprops>();
</script>
<style lang="scss" scoped>
.num {
font-size: 28px;
}
.right-icon {
position: absolute;
top: 50%;
right: 20px;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
border-radius: 50%;
transform: translateY(-50%);
}
</style>

View File

@ -11,7 +11,7 @@
:margin="[12, 12]"
>
<grid-item v-for="item in layout" :key="item.i" :x="item.x" :y="item.y" :w="item.w" :h="item.h" :i="item.i">
<component :is="item.render" />
<component :is="item.component" v-bind="item.props" />
</grid-item>
</grid-layout>
</bd-page>
@ -24,18 +24,101 @@ meta:
</route>
<script lang="tsx" setup>
import dayjs from 'dayjs';
import Statistics from './components/Statistics.vue';
import AddUser from './components/AddUser.vue';
import AddGroup from './components/AddGroup.vue';
const layout = ref([
{ x: 0, y: 0, w: 3, h: 4, i: '0', render: Statistics },
{ x: 3, y: 0, w: 3, h: 4, i: '1', render: Statistics },
{ x: 6, y: 0, w: 3, h: 4, i: '2', render: Statistics },
{ x: 9, y: 0, w: 3, h: 4, i: '3', render: Statistics },
{ x: 0, y: 1, w: 12, h: 12, i: '4', render: Statistics },
{ x: 4, y: 1, w: 12, h: 12, i: '5', render: Statistics }
// API
import { statisticsCountnumGet } from '@/api/statistic';
const layout = ref<any[]>([
{
x: 0,
y: 0,
w: 3,
h: 4,
i: 'register_count',
props: { title: '当日注册人数', value: 0, icon: 'i-bd-people' },
component: (props: any) => {
return <Statistics {...props} />;
}
},
{
x: 3,
y: 0,
w: 3,
h: 4,
i: 'group_create_count',
props: { title: '当日新建群', value: 0, icon: 'i-bd-peoples-two' },
component: (props: any) => {
return <Statistics {...props} />;
}
},
{
x: 6,
y: 0,
w: 3,
h: 4,
i: 'user_total_count',
props: { title: '当前总用户', value: 0, icon: 'i-bd-ranking' },
component: (props: any) => {
return <Statistics {...props} />;
}
},
{
x: 9,
y: 0,
w: 3,
h: 4,
i: 'group_total_count',
props: { title: '当前总群数', value: 0, icon: 'i-bd-data-sheet' },
component: (props: any) => {
return <Statistics {...props} />;
}
},
{
x: 0,
y: 1,
w: 12,
h: 12,
i: '4',
component: (props: any) => {
return <AddUser {...props} />;
}
},
{
x: 4,
y: 1,
w: 12,
h: 12,
i: '5',
component: (props: any) => {
return <AddGroup {...props} />;
}
}
]);
const settings = ref(false);
const formData = reactive({
date: ''
});
const getStatisticsCountnum = () => {
statisticsCountnumGet(formData).then((res: any) => {
for (const key in res) {
layout.value.map(item => {
if (item.i === key) {
item.props.value = res[key];
}
});
}
});
};
onMounted(() => {
formData.date = dayjs().format('YYYY-MM-DD');
getStatisticsCountnum();
});
</script>
<style lang="scss" scoped>

View File

@ -10,6 +10,7 @@ declare module 'vue' {
AddNode: typeof import('./../components/bdWorkflow/nodes/addNode.vue')['default']
Approver: typeof import('./../components/bdWorkflow/nodes/approver.vue')['default']
BdPage: typeof import('./../components/BdPage/index.vue')['default']
BdStatistic: typeof import('./../components/BdStatistic/index.vue')['default']
BdWorkflow: typeof import('./../components/bdWorkflow/index.vue')['default']
Branch: typeof import('./../components/bdWorkflow/nodes/branch.vue')['default']
HelloWorld: typeof import('./../components/HelloWorld.vue')['default']