feat: 完善仪表盘访客地域分布区块内容

This commit is contained in:
Charles7c 2023-09-09 15:09:05 +08:00
parent 83b2e2a7c0
commit dc1691f019
19 changed files with 253 additions and 140 deletions

View File

@ -17,6 +17,7 @@
package top.charles7c.cnadmin.monitor.mapper;
import java.util.List;
import java.util.Map;
import top.charles7c.cnadmin.common.base.BaseMapper;
import top.charles7c.cnadmin.monitor.model.entity.LogDO;
@ -44,4 +45,11 @@ public interface LogMapper extends BaseMapper<LogDO> {
* @return 仪表盘热门模块列表
*/
List<DashboardPopularModuleVO> selectListDashboardPopularModule();
/**
* 查询仪表盘访客地域分布信息
*
* @return 仪表盘访客地域分布信息
*/
List<Map<String, Object>> selectListDashboardGeoDistribution();
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.monitor.model.vo;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 仪表盘-访客地域分布信息
*
* @author Charles7c
* @since 2023/9/9 12:07
*/
@Data
@Schema(description = "仪表盘-访客地域分布信息")
public class DashboardGeoDistributionVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 地点列表
*/
@Schema(description = "地点列表", example = "[\"中国北京北京市\",\"中国广东省深圳市\"]")
private List<String> locations;
/**
* 地点 IP 统计信息
*/
@Schema(description = "地点 IP 统计信息",
example = "[{\"name\":\"中国北京北京市\",\"value\":1000},{\"name\":\"中国广东省深圳市\",\"value\": 500}]")
private List<Map<String, Object>> locationIpStatistics;
}

View File

@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
* @since 2023/9/9 9:52
*/
@Data
@Schema(description = "仪表盘-热门模块信息")
public class DashboardPopularModuleVO implements Serializable {
private static final long serialVersionUID = 1L;

View File

@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
* @since 2023/9/8 21:32
*/
@Data
@Schema(description = "仪表盘-总计信息")
public class DashboardTotalVO implements Serializable {
private static final long serialVersionUID = 1L;

View File

@ -18,6 +18,7 @@ package top.charles7c.cnadmin.monitor.service;
import java.util.List;
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
import top.charles7c.cnadmin.system.model.vo.DashboardAnnouncementVO;
@ -44,6 +45,13 @@ public interface DashboardService {
*/
List<DashboardPopularModuleVO> listPopularModule();
/**
* 查询访客地域分布信息
*
* @return 访客地域分布信息
*/
DashboardGeoDistributionVO getGeoDistribution();
/**
* 查询公告列表
*

View File

@ -17,6 +17,7 @@
package top.charles7c.cnadmin.monitor.service;
import java.util.List;
import java.util.Map;
import top.charles7c.cnadmin.common.model.query.PageQuery;
import top.charles7c.cnadmin.common.model.vo.PageDataVO;
@ -87,5 +88,12 @@ public interface LogService {
*
* @return 仪表盘热门模块列表
*/
List<DashboardPopularModuleVO> listPopularModule();
List<DashboardPopularModuleVO> listDashboardPopularModule();
/**
* 查询仪表盘访客地域分布信息
*
* @return 仪表盘访客地域分布信息
*/
List<Map<String, Object>> listDashboardGeoDistribution();
}

View File

@ -18,14 +18,18 @@ package top.charles7c.cnadmin.monitor.service.impl;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
import top.charles7c.cnadmin.monitor.service.DashboardService;
@ -61,7 +65,7 @@ public class DashboardServiceImpl implements DashboardService {
@Override
public List<DashboardPopularModuleVO> listPopularModule() {
List<DashboardPopularModuleVO> popularModuleList = logService.listPopularModule();
List<DashboardPopularModuleVO> popularModuleList = logService.listDashboardPopularModule();
for (DashboardPopularModuleVO popularModule : popularModuleList) {
Long todayPvCount = popularModule.getTodayPvCount();
Long yesterdayPvCount = popularModule.getYesterdayPvCount();
@ -73,6 +77,16 @@ public class DashboardServiceImpl implements DashboardService {
return popularModuleList;
}
@Override
public DashboardGeoDistributionVO getGeoDistribution() {
List<Map<String, Object>> locationIpStatistics = logService.listDashboardGeoDistribution();
DashboardGeoDistributionVO geoDistribution = new DashboardGeoDistributionVO();
geoDistribution.setLocationIpStatistics(locationIpStatistics);
geoDistribution.setLocations(
locationIpStatistics.stream().map(m -> Convert.toStr(m.get("name"))).collect(Collectors.toList()));
return geoDistribution;
}
@Override
public List<DashboardAnnouncementVO> listAnnouncement() {
return announcementService.listDashboard();

View File

@ -17,6 +17,7 @@
package top.charles7c.cnadmin.monitor.service.impl;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
@ -151,10 +152,15 @@ public class LogServiceImpl implements LogService {
}
@Override
public List<DashboardPopularModuleVO> listPopularModule() {
public List<DashboardPopularModuleVO> listDashboardPopularModule() {
return logMapper.selectListDashboardPopularModule();
}
@Override
public List<Map<String, Object>> listDashboardGeoDistribution() {
return logMapper.selectListDashboardGeoDistribution();
}
/**
* 填充数据
*

View File

@ -22,4 +22,14 @@
ORDER BY `pvCount` DESC
LIMIT 10
</select>
<select id="selectListDashboardGeoDistribution" resultType="java.util.Map">
SELECT
`location` AS name,
COUNT(DISTINCT `client_ip`) AS value
FROM `sys_log`
GROUP BY `location`
ORDER BY COUNT(DISTINCT `client_ip`) DESC
LIMIT 10
</select>
</mapper>

View File

@ -31,7 +31,7 @@ import top.charles7c.cnadmin.system.enums.AnnouncementTypeEnum;
* @since 2023/8/20 10:55
*/
@Data
@Schema(description = "仪表盘公告信息")
@Schema(description = "仪表盘-公告信息")
public class DashboardAnnouncementVO implements Serializable {
private static final long serialVersionUID = 1L;

View File

@ -16,6 +16,11 @@ export interface DashboardPopularModuleRecord {
newPvFromYesterday: number;
}
export interface DashboardGeoDistributionRecord {
locations: string[];
locationIpStatistics: [];
}
export interface DashboardAnnouncementRecord {
id: string;
title: string;
@ -32,6 +37,12 @@ export function listPopularModule() {
);
}
export function getGeoDistribution() {
return axios.get<DashboardGeoDistributionRecord>(
`${BASE_URL}/geo/distribution`
);
}
export function listAnnouncement() {
return axios.get<DashboardAnnouncementRecord[]>(`${BASE_URL}/announcement`);
}
@ -43,15 +54,4 @@ export interface ContentDataRecord {
export function queryContentData() {
return axios.get<ContentDataRecord[]>('/api/content-data');
}
export interface PopularRecord {
key: number;
clickNumber: string;
title: string;
increases: number;
}
export function queryPopularList(params: { type: string }) {
return axios.get<TableData[]>('/api/popular/list', { params });
}
}

View File

@ -1,114 +0,0 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{
padding: '20px',
}"
>
<template #title>
{{ $t('workplace.categoriesPercent') }}
</template>
<Chart height="310px" :option="chartOption" />
</a-card>
</a-spin>
</template>
<script lang="ts" setup>
import useLoading from '@/hooks/loading';
import useChartOption from '@/hooks/chart-option';
const { loading } = useLoading();
const { chartOption } = useChartOption((isDark) => {
// echarts support https://echarts.apache.org/zh/theme-builder.html
// It's not used here
return {
legend: {
left: 'center',
data: ['纯文本', '图文类', '视频类'],
bottom: 0,
icon: 'circle',
itemWidth: 8,
textStyle: {
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderWidth: 0,
},
},
tooltip: {
show: true,
trigger: 'item',
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '40%',
style: {
text: '内容量',
textAlign: 'center',
fill: isDark ? '#ffffffb3' : '#4E5969',
fontSize: 14,
},
},
{
type: 'text',
left: 'center',
top: '50%',
style: {
text: '928,531',
textAlign: 'center',
fill: isDark ? '#ffffffb3' : '#1D2129',
fontSize: 16,
fontWeight: 500,
},
},
],
},
series: [
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
label: {
formatter: '{d}%',
fontSize: 14,
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderColor: isDark ? '#232324' : '#fff',
borderWidth: 1,
},
data: [
{
value: [148564],
name: '纯文本',
itemStyle: {
color: isDark ? '#3D72F6' : '#249EFF',
},
},
{
value: [334271],
name: '图文类',
itemStyle: {
color: isDark ? '#A079DC' : '#313CA9',
},
},
{
value: [445694],
name: '视频类',
itemStyle: {
color: isDark ? '#6CAAF5' : '#21CCFF',
},
},
],
},
],
};
});
</script>
<style scoped lang="less"></style>

View File

@ -3,8 +3,8 @@
class="general-card"
:title="$t('workplace.docs')"
:header-style="{ paddingBottom: 0 }"
:body-style="{ paddingTop: 0 }"
style="height: 166px"
:body-style="{ paddingTop: '10px', paddingBottom: '10px' }"
style="height: 198px"
>
<template #extra>
<a-link href="https://doc.charles7c.top" target="_blank" rel="noopener">{{
@ -48,6 +48,24 @@
{{ $t('workplace.docs.changelog') }}
</a-link>
</a-col>
<a-col :span="12">
<a-link
href="https://blog.charles7c.top"
target="_blank"
rel="noopener"
>
{{ $t('workplace.docs.authorSite') }}👋
</a-link>
</a-col>
<a-col :span="12">
<a-link
href="https://doc.charles7c.top/require.html"
target="_blank"
rel="noopener"
>
{{ $t('workplace.docs.require') }}
</a-link>
</a-col>
</a-row>
</a-card>
</template>
@ -58,3 +76,4 @@
color: rgb(var(--gray-8));
}
</style>
<script setup lang="ts"></script>

View File

@ -0,0 +1,90 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{
padding: '0 20px',
}"
>
<template #title>
{{ $t('workplace.geoDistribution') }}
</template>
<Chart height="480px" :option="chartOption" />
</a-card>
</a-spin>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import useChartOption from '@/hooks/chart-option';
import {
DashboardGeoDistributionRecord,
getGeoDistribution,
} from '@/api/common/dashboard';
const { loading, setLoading } = useLoading();
const statisticsData = ref<DashboardGeoDistributionRecord>({
locations: [],
locationIpStatistics: [],
});
/**
* 查询访客地域分布信息
*/
const getData = async () => {
try {
setLoading(true);
const { data } = await getGeoDistribution();
statisticsData.value = data;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
getData();
const { chartOption } = useChartOption((isDark) => {
// echarts support https://echarts.apache.org/zh/theme-builder.html
// It's not used here
return {
legend: {
left: 'center',
data: statisticsData.value.locations,
bottom: -5,
icon: 'circle',
itemStyle: {
borderWidth: 0,
},
},
tooltip: {
show: true,
trigger: 'item',
},
series: [
{
type: 'pie',
radius: '70%',
label: {
formatter: '{d}%',
fontSize: 14,
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderColor: isDark ? '#232324' : '#fff',
borderWidth: 1,
},
data: statisticsData.value.locationIpStatistics,
},
],
};
});
</script>
<style scoped lang="less">
.general-card {
min-height: 566px;
}
</style>

View File

@ -3,7 +3,7 @@
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '17px 20px 21px 20px' }"
:body-style="{ padding: '17px 20px 20px 20px' }"
>
<template #title>
{{ $t('workplace.popularModule') }}
@ -13,7 +13,7 @@
:data="dataList"
:pagination="false"
:bordered="false"
:scroll="{ x: '100%', y: '310px' }"
:scroll="{ x: '100%', y: '484px' }"
>
<template #columns>
<a-table-column title="排名">
@ -89,7 +89,7 @@
<style scoped lang="less">
.general-card {
min-height: 395px;
min-height: 400px;
}
:deep(.arco-table-tr) {
height: 44px;

View File

@ -46,7 +46,7 @@
import DataPanel from './components/data-panel.vue';
import ContentChart from './components/content-chart.vue';
import PopularModule from './components/popular-module.vue';
import CategoriesPercent from './components/categories-percent.vue';
import CategoriesPercent from './components/geo-distribution.vue';
import RecentlyVisited from './components/recently-visited.vue';
import QuickOperation from './components/quick-operation.vue';
import Announcement from './components/announcement.vue';

View File

@ -14,6 +14,8 @@ export default {
'workplace.docs.userGuide': 'User Guide',
'workplace.docs.faq': 'FAQ',
'workplace.docs.changelog': 'Change Log',
'workplace.docs.authorSite': 'Author Site',
'workplace.docs.require': 'Require',
'workplace.announcement': 'Announcement',
'workplace.recently.visited': 'Recently Visited',
'workplace.record.nodata': 'No data',
@ -23,8 +25,8 @@ export default {
'workplace.loadMore': 'More',
'workplace.viewMore': 'More',
'workplace.contentData': 'Content Data',
'workplace.popularModule': 'Popular Module',
'workplace.categoriesPercent': 'Categories Percent',
'workplace.popularModule': 'Popular Module(Top10)',
'workplace.geoDistribution': 'Geo Distribution(Top10)',
'workplace.unit.pecs': 'pecs',
'workplace.unit.times': 'times',
};

View File

@ -14,6 +14,8 @@ export default {
'workplace.docs.userGuide': '使用指南',
'workplace.docs.faq': '常见问题',
'workplace.docs.changelog': '更新日志',
'workplace.docs.authorSite': '作者主页',
'workplace.docs.require': '需求墙',
'workplace.announcement': '公告',
'workplace.recently.visited': '最近访问',
'workplace.record.nodata': '暂无数据',
@ -23,8 +25,8 @@ export default {
'workplace.loadMore': '加载更多',
'workplace.viewMore': '查看更多',
'workplace.contentData': '内容数据',
'workplace.popularModule': '热门模块',
'workplace.categoriesPercent': '内容类型占比',
'workplace.popularModule': '热门模块Top10',
'workplace.geoDistribution': '访客地域分布Top10',
'workplace.unit.pecs': '个',
'workplace.unit.times': '次',
};

View File

@ -30,6 +30,7 @@ import org.springframework.web.bind.annotation.RestController;
import top.charles7c.cnadmin.common.model.vo.R;
import top.charles7c.cnadmin.monitor.annotation.Log;
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
import top.charles7c.cnadmin.monitor.service.DashboardService;
@ -63,6 +64,12 @@ public class DashboardController {
return R.ok(dashboardService.listPopularModule());
}
@Operation(summary = "查询访客地域分布信息", description = "查询访客地域分布信息")
@GetMapping("/geo/distribution")
public R<DashboardGeoDistributionVO> getGeoDistribution() {
return R.ok(dashboardService.getGeoDistribution());
}
@Operation(summary = "查询公告列表", description = "查询公告列表")
@GetMapping("/announcement")
public R<List<DashboardAnnouncementVO>> listAnnouncement() {