mirror of
https://github.com/TangSengDaoDao/TangSengDaoDaoManager
synced 2025-06-05 00:28:11 +00:00
feat: ✨新增首页
This commit is contained in:
parent
2ab7a579ce
commit
2cf8c7e791
@ -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
23
pnpm-lock.yaml
generated
@ -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
26
src/api/statistic.ts
Normal 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
27
src/hooks/useEcharts.ts
Normal 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);
|
||||
});
|
||||
};
|
166
src/pages/home/components/AddGroup.vue
Normal file
166
src/pages/home/components/AddGroup.vue
Normal 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>
|
142
src/pages/home/components/AddUser.vue
Normal file
142
src/pages/home/components/AddUser.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@ -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']
|
||||
|
Loading…
x
Reference in New Issue
Block a user