feat: 完善仪表盘访问趋势区块内容
This commit is contained in:
parent
dc1691f019
commit
a1c20afb1b
@ -19,8 +19,11 @@ package top.charles7c.cnadmin.monitor.mapper;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import top.charles7c.cnadmin.common.base.BaseMapper;
|
import top.charles7c.cnadmin.common.base.BaseMapper;
|
||||||
import top.charles7c.cnadmin.monitor.model.entity.LogDO;
|
import top.charles7c.cnadmin.monitor.model.entity.LogDO;
|
||||||
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardAccessTrendVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
||||||
|
|
||||||
@ -39,6 +42,16 @@ public interface LogMapper extends BaseMapper<LogDO> {
|
|||||||
*/
|
*/
|
||||||
DashboardTotalVO selectDashboardTotal();
|
DashboardTotalVO selectDashboardTotal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询仪表盘访问趋势信息
|
||||||
|
*
|
||||||
|
* @param days
|
||||||
|
* 日期数
|
||||||
|
*
|
||||||
|
* @return 仪表盘访问趋势信息
|
||||||
|
*/
|
||||||
|
List<DashboardAccessTrendVO> selectListDashboardAccessTrend(@Param("days") Integer days);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询仪表盘热门模块列表
|
* 查询仪表盘热门模块列表
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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 lombok.Data;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仪表盘-访问趋势信息
|
||||||
|
*
|
||||||
|
* @author Charles7c
|
||||||
|
* @since 2023/9/9 20:20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "仪表盘-访问趋势信息")
|
||||||
|
public class DashboardAccessTrendVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日期
|
||||||
|
*/
|
||||||
|
@Schema(description = "日期", example = "2023-08-08")
|
||||||
|
private String date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览量(PV)
|
||||||
|
*/
|
||||||
|
@Schema(description = "浏览量(PV)", example = "1000")
|
||||||
|
private Long pvCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP 数
|
||||||
|
*/
|
||||||
|
@Schema(description = "IP 数", example = "500")
|
||||||
|
private Long ipCount;
|
||||||
|
}
|
@ -18,6 +18,7 @@ package top.charles7c.cnadmin.monitor.service;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardAccessTrendVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
||||||
@ -38,6 +39,15 @@ public interface DashboardService {
|
|||||||
*/
|
*/
|
||||||
DashboardTotalVO getTotal();
|
DashboardTotalVO getTotal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询访问趋势信息
|
||||||
|
*
|
||||||
|
* @param days
|
||||||
|
* 日期数
|
||||||
|
* @return 访问趋势信息
|
||||||
|
*/
|
||||||
|
List<DashboardAccessTrendVO> listAccessTrend(Integer days);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询热门模块列表
|
* 查询热门模块列表
|
||||||
*
|
*
|
||||||
|
@ -83,6 +83,13 @@ public interface LogService {
|
|||||||
*/
|
*/
|
||||||
DashboardTotalVO getDashboardTotal();
|
DashboardTotalVO getDashboardTotal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询仪表盘访问趋势信息
|
||||||
|
*
|
||||||
|
* @return 仪表盘访问趋势信息
|
||||||
|
*/
|
||||||
|
List<DashboardAccessTrendVO> listDashboardAccessTrend(Integer days);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询仪表盘热门模块列表
|
* 查询仪表盘热门模块列表
|
||||||
*
|
*
|
||||||
|
@ -29,6 +29,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.NumberUtil;
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
|
||||||
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardAccessTrendVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
||||||
@ -63,6 +64,11 @@ public class DashboardServiceImpl implements DashboardService {
|
|||||||
return totalVO;
|
return totalVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DashboardAccessTrendVO> listAccessTrend(Integer days) {
|
||||||
|
return logService.listDashboardAccessTrend(days);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DashboardPopularModuleVO> listPopularModule() {
|
public List<DashboardPopularModuleVO> listPopularModule() {
|
||||||
List<DashboardPopularModuleVO> popularModuleList = logService.listDashboardPopularModule();
|
List<DashboardPopularModuleVO> popularModuleList = logService.listDashboardPopularModule();
|
||||||
|
@ -151,6 +151,11 @@ public class LogServiceImpl implements LogService {
|
|||||||
return logMapper.selectDashboardTotal();
|
return logMapper.selectDashboardTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DashboardAccessTrendVO> listDashboardAccessTrend(Integer days) {
|
||||||
|
return logMapper.selectListDashboardAccessTrend(days);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DashboardPopularModuleVO> listDashboardPopularModule() {
|
public List<DashboardPopularModuleVO> listDashboardPopularModule() {
|
||||||
return logMapper.selectListDashboardPopularModule();
|
return logMapper.selectListDashboardPopularModule();
|
||||||
|
@ -9,6 +9,18 @@
|
|||||||
(SELECT COUNT(*) FROM `sys_log` WHERE DATE(`create_time`) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)) AS yesterdayPvCount
|
(SELECT COUNT(*) FROM `sys_log` WHERE DATE(`create_time`) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)) AS yesterdayPvCount
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectListDashboardAccessTrend"
|
||||||
|
resultType="top.charles7c.cnadmin.monitor.model.vo.DashboardAccessTrendVO">
|
||||||
|
SELECT
|
||||||
|
DATE(`create_time`) AS date,
|
||||||
|
COUNT(*) AS pvCount,
|
||||||
|
COUNT(DISTINCT `client_ip`) AS ipCount
|
||||||
|
FROM `sys_log`
|
||||||
|
GROUP BY DATE(`create_time`)
|
||||||
|
ORDER BY DATE(`create_time`) DESC
|
||||||
|
LIMIT #{days}
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectListDashboardPopularModule"
|
<select id="selectListDashboardPopularModule"
|
||||||
resultType="top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO">
|
resultType="top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO">
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { TableData } from '@arco-design/web-vue/es/table/interface';
|
|
||||||
|
|
||||||
const BASE_URL = '/dashboard';
|
const BASE_URL = '/dashboard';
|
||||||
|
|
||||||
@ -10,6 +9,12 @@ export interface DashboardTotalRecord {
|
|||||||
newPvFromYesterday: number;
|
newPvFromYesterday: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DashboardAccessTrendRecord {
|
||||||
|
date: string;
|
||||||
|
pvCount: number;
|
||||||
|
ipCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DashboardPopularModuleRecord {
|
export interface DashboardPopularModuleRecord {
|
||||||
module: string;
|
module: string;
|
||||||
pvCount: number;
|
pvCount: number;
|
||||||
@ -31,6 +36,12 @@ export function getTotal() {
|
|||||||
return axios.get<DashboardTotalRecord>(`${BASE_URL}/total`);
|
return axios.get<DashboardTotalRecord>(`${BASE_URL}/total`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listAccessTrend(days: number) {
|
||||||
|
return axios.get<DashboardAccessTrendRecord[]>(
|
||||||
|
`${BASE_URL}/access/trend/${days}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function listPopularModule() {
|
export function listPopularModule() {
|
||||||
return axios.get<DashboardPopularModuleRecord[]>(
|
return axios.get<DashboardPopularModuleRecord[]>(
|
||||||
`${BASE_URL}/popular/module`
|
`${BASE_URL}/popular/module`
|
||||||
@ -46,12 +57,3 @@ export function getGeoDistribution() {
|
|||||||
export function listAnnouncement() {
|
export function listAnnouncement() {
|
||||||
return axios.get<DashboardAnnouncementRecord[]>(`${BASE_URL}/announcement`);
|
return axios.get<DashboardAnnouncementRecord[]>(`${BASE_URL}/announcement`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContentDataRecord {
|
|
||||||
x: string;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryContentData() {
|
|
||||||
return axios.get<ContentDataRecord[]>('/api/content-data');
|
|
||||||
}
|
|
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<a-spin :loading="loading" style="width: 100%">
|
||||||
|
<a-card
|
||||||
|
class="general-card"
|
||||||
|
:header-style="{ paddingBottom: 0 }"
|
||||||
|
:body-style="{
|
||||||
|
paddingTop: '20px',
|
||||||
|
}"
|
||||||
|
:title="$t('workplace.accessTrend')"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<a-radio-group
|
||||||
|
v-model:model-value="dateRange"
|
||||||
|
type="button"
|
||||||
|
@change="handleDateRangeChange as any"
|
||||||
|
>
|
||||||
|
<a-radio :value="7">
|
||||||
|
{{ $t('workplace.accessTrend.dateRange7') }}
|
||||||
|
</a-radio>
|
||||||
|
<a-radio :value="30">
|
||||||
|
{{ $t('workplace.accessTrend.dateRange30') }}
|
||||||
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</template>
|
||||||
|
<Chart height="289px" :option="chartOption" />
|
||||||
|
</a-card>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import useLoading from '@/hooks/loading';
|
||||||
|
import {
|
||||||
|
DashboardAccessTrendRecord,
|
||||||
|
listAccessTrend,
|
||||||
|
} from '@/api/common/dashboard';
|
||||||
|
import useChartOption from '@/hooks/chart-option';
|
||||||
|
import { ToolTipFormatterParams } from '@/types/echarts';
|
||||||
|
|
||||||
|
const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => {
|
||||||
|
return items
|
||||||
|
.map(
|
||||||
|
(el) => `<div class="content-panel">
|
||||||
|
<p>
|
||||||
|
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
|
||||||
|
<span>${el.seriesName}</span>
|
||||||
|
</p>
|
||||||
|
<span class="tooltip-value">
|
||||||
|
${el.value}
|
||||||
|
</span>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.join('');
|
||||||
|
};
|
||||||
|
const { loading, setLoading } = useLoading(true);
|
||||||
|
const dateRange = ref(30);
|
||||||
|
const xAxis = ref<string[]>([]);
|
||||||
|
const pvStatisticsData = ref<number[]>([]);
|
||||||
|
const ipStatisticsData = ref<number[]>([]);
|
||||||
|
const { chartOption } = useChartOption((isDark) => {
|
||||||
|
return {
|
||||||
|
grid: {
|
||||||
|
left: '30',
|
||||||
|
right: '0',
|
||||||
|
top: '10',
|
||||||
|
bottom: '50',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
bottom: -3,
|
||||||
|
icon: 'circle',
|
||||||
|
textStyle: {
|
||||||
|
color: '#4E5969',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
offset: 2,
|
||||||
|
data: xAxis.value,
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLabel: {
|
||||||
|
color: '#4E5969',
|
||||||
|
formatter(value: number, idx: number) {
|
||||||
|
if (idx === 0) return '';
|
||||||
|
if (idx === xAxis.value.length - 1) return '';
|
||||||
|
return `${value}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
interval: (idx: number) => {
|
||||||
|
if (idx === 0) return false;
|
||||||
|
return idx !== xAxis.value.length - 1;
|
||||||
|
},
|
||||||
|
lineStyle: {
|
||||||
|
color: isDark ? '#3F3F3F' : '#E5E8EF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#23ADFF',
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter(value: any, idx: number) {
|
||||||
|
if (idx === 0) return value;
|
||||||
|
return `${value}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: isDark ? '#3F3F3F' : '#E5E8EF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
show: true,
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter(params) {
|
||||||
|
const [firstElement] = params as ToolTipFormatterParams[];
|
||||||
|
return `<div>
|
||||||
|
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
||||||
|
${tooltipItemsHtmlString(params as ToolTipFormatterParams[])}
|
||||||
|
</div>`;
|
||||||
|
},
|
||||||
|
className: 'echarts-tooltip-diy',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '浏览量(PV)',
|
||||||
|
data: pvStatisticsData.value,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: false,
|
||||||
|
color: isDark ? '#3D72F6' : '#246EFF',
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 10,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series',
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#E0E3FF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'IP数',
|
||||||
|
data: ipStatisticsData.value,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
showSymbol: false,
|
||||||
|
color: isDark ? '#A079DC' : '#00B2FF',
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 10,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series',
|
||||||
|
itemStyle: {
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#E2F2FF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询趋势图信息
|
||||||
|
*
|
||||||
|
* @param days 日期数
|
||||||
|
*/
|
||||||
|
const getList = async (days: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
xAxis.value = [];
|
||||||
|
pvStatisticsData.value = [];
|
||||||
|
ipStatisticsData.value = [];
|
||||||
|
const { data: chartData } = await listAccessTrend(days);
|
||||||
|
chartData.forEach((el: DashboardAccessTrendRecord) => {
|
||||||
|
xAxis.value.unshift(el.date);
|
||||||
|
pvStatisticsData.value.unshift(el.pvCount);
|
||||||
|
ipStatisticsData.value.unshift(el.ipCount);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// you can report use errorHandler or other
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换日期范围
|
||||||
|
*
|
||||||
|
* @param days 日期数
|
||||||
|
*/
|
||||||
|
const handleDateRangeChange = (days: number) => {
|
||||||
|
getList(days);
|
||||||
|
};
|
||||||
|
getList(30);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less"></style>
|
@ -1,200 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-spin :loading="loading" style="width: 100%">
|
|
||||||
<a-card
|
|
||||||
class="general-card"
|
|
||||||
:header-style="{ paddingBottom: 0 }"
|
|
||||||
:body-style="{
|
|
||||||
paddingTop: '20px',
|
|
||||||
}"
|
|
||||||
:title="$t('workplace.contentData')"
|
|
||||||
>
|
|
||||||
<template #extra>
|
|
||||||
<a-link>{{ $t('workplace.viewMore') }}</a-link>
|
|
||||||
</template>
|
|
||||||
<Chart height="289px" :option="chartOption" />
|
|
||||||
</a-card>
|
|
||||||
</a-spin>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { graphic } from 'echarts';
|
|
||||||
import useLoading from '@/hooks/loading';
|
|
||||||
import { queryContentData, ContentDataRecord } from '@/api/common/dashboard';
|
|
||||||
import useChartOption from '@/hooks/chart-option';
|
|
||||||
import { ToolTipFormatterParams } from '@/types/echarts';
|
|
||||||
import { AnyObject } from '@/types/global';
|
|
||||||
|
|
||||||
function graphicFactory(side: AnyObject) {
|
|
||||||
return {
|
|
||||||
type: 'text',
|
|
||||||
bottom: '8',
|
|
||||||
...side,
|
|
||||||
style: {
|
|
||||||
text: '',
|
|
||||||
textAlign: 'center',
|
|
||||||
fill: '#4E5969',
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const { loading, setLoading } = useLoading(true);
|
|
||||||
const xAxis = ref<string[]>([]);
|
|
||||||
const chartsData = ref<number[]>([]);
|
|
||||||
const graphicElements = ref([
|
|
||||||
graphicFactory({ left: '2.6%' }),
|
|
||||||
graphicFactory({ right: 0 }),
|
|
||||||
]);
|
|
||||||
const { chartOption } = useChartOption(() => {
|
|
||||||
return {
|
|
||||||
grid: {
|
|
||||||
left: '2.6%',
|
|
||||||
right: '0',
|
|
||||||
top: '10',
|
|
||||||
bottom: '30',
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
offset: 2,
|
|
||||||
data: xAxis.value,
|
|
||||||
boundaryGap: false,
|
|
||||||
axisLabel: {
|
|
||||||
color: '#4E5969',
|
|
||||||
formatter(value: number, idx: number) {
|
|
||||||
if (idx === 0) return '';
|
|
||||||
if (idx === xAxis.value.length - 1) return '';
|
|
||||||
return `${value}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
interval: (idx: number) => {
|
|
||||||
if (idx === 0) return false;
|
|
||||||
if (idx === xAxis.value.length - 1) return false;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
lineStyle: {
|
|
||||||
color: '#E5E8EF',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisPointer: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#23ADFF',
|
|
||||||
width: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
axisLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
formatter(value: any, idx: number) {
|
|
||||||
if (idx === 0) return value;
|
|
||||||
return `${value}k`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
type: 'dashed',
|
|
||||||
color: '#E5E8EF',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
formatter(params) {
|
|
||||||
const [firstElement] = params as ToolTipFormatterParams[];
|
|
||||||
return `<div>
|
|
||||||
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
|
||||||
<div class="content-panel"><span>总内容量</span><span class="tooltip-value">${(
|
|
||||||
Number(firstElement.value) * 10000
|
|
||||||
).toLocaleString()}</span></div>
|
|
||||||
</div>`;
|
|
||||||
},
|
|
||||||
className: 'echarts-tooltip-diy',
|
|
||||||
},
|
|
||||||
graphic: {
|
|
||||||
elements: graphicElements.value,
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
data: chartsData.value,
|
|
||||||
type: 'line',
|
|
||||||
smooth: true,
|
|
||||||
// symbol: 'circle',
|
|
||||||
symbolSize: 12,
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series',
|
|
||||||
itemStyle: {
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
lineStyle: {
|
|
||||||
width: 3,
|
|
||||||
color: new graphic.LinearGradient(0, 0, 1, 0, [
|
|
||||||
{
|
|
||||||
offset: 0,
|
|
||||||
color: 'rgba(30, 231, 255, 1)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 0.5,
|
|
||||||
color: 'rgba(36, 154, 255, 1)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: 'rgba(111, 66, 251, 1)',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
showSymbol: false,
|
|
||||||
areaStyle: {
|
|
||||||
opacity: 0.8,
|
|
||||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
|
||||||
{
|
|
||||||
offset: 0,
|
|
||||||
color: 'rgba(17, 126, 255, 0.16)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
offset: 1,
|
|
||||||
color: 'rgba(17, 128, 255, 0)',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const fetchData = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const { data: chartData } = await queryContentData();
|
|
||||||
chartData.forEach((el: ContentDataRecord, idx: number) => {
|
|
||||||
xAxis.value.push(el.x);
|
|
||||||
chartsData.value.push(el.y);
|
|
||||||
if (idx === 0) {
|
|
||||||
graphicElements.value[0].style.text = el.x;
|
|
||||||
}
|
|
||||||
if (idx === chartData.length - 1) {
|
|
||||||
graphicElements.value[1].style.text = el.x;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
// you can report use errorHandler or other
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchData();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less"></style>
|
|
@ -4,7 +4,7 @@
|
|||||||
:title="$t('workplace.docs')"
|
:title="$t('workplace.docs')"
|
||||||
:header-style="{ paddingBottom: 0 }"
|
:header-style="{ paddingBottom: 0 }"
|
||||||
:body-style="{ paddingTop: '10px', paddingBottom: '10px' }"
|
:body-style="{ paddingTop: '10px', paddingBottom: '10px' }"
|
||||||
style="height: 198px"
|
style="height: 200px"
|
||||||
>
|
>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-link href="https://doc.charles7c.top" target="_blank" rel="noopener">{{
|
<a-link href="https://doc.charles7c.top" target="_blank" rel="noopener">{{
|
||||||
@ -70,10 +70,11 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.arco-card-body .arco-link {
|
.arco-card-body .arco-link {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
color: rgb(var(--gray-8));
|
color: rgb(var(--gray-8));
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script setup lang="ts"></script>
|
|
@ -4,7 +4,7 @@
|
|||||||
class="general-card"
|
class="general-card"
|
||||||
:header-style="{ paddingBottom: '0' }"
|
:header-style="{ paddingBottom: '0' }"
|
||||||
:body-style="{
|
:body-style="{
|
||||||
padding: '0 20px',
|
padding: '0 20px 15px 20px',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
@ -58,6 +58,9 @@
|
|||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
|
textStyle: {
|
||||||
|
color: '#4E5969',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
show: true,
|
show: true,
|
||||||
@ -66,15 +69,15 @@
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: '70%',
|
radius: '65%',
|
||||||
label: {
|
label: {
|
||||||
formatter: '{d}%',
|
formatter: '{d}%',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
||||||
},
|
},
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderColor: isDark ? '#232324' : '#fff',
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
borderColor: '#D9F6FF',
|
||||||
},
|
},
|
||||||
data: statisticsData.value.locationIpStatistics,
|
data: statisticsData.value.locationIpStatistics,
|
||||||
},
|
},
|
||||||
@ -85,6 +88,6 @@
|
|||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.general-card {
|
.general-card {
|
||||||
min-height: 566px;
|
min-height: 568px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Banner from './components/banner.vue';
|
import Banner from './components/banner.vue';
|
||||||
import DataPanel from './components/data-panel.vue';
|
import DataPanel from './components/data-panel.vue';
|
||||||
import ContentChart from './components/content-chart.vue';
|
import ContentChart from './components/access-trend.vue';
|
||||||
import PopularModule from './components/popular-module.vue';
|
import PopularModule from './components/popular-module.vue';
|
||||||
import CategoriesPercent from './components/geo-distribution.vue';
|
import CategoriesPercent from './components/geo-distribution.vue';
|
||||||
import RecentlyVisited from './components/recently-visited.vue';
|
import RecentlyVisited from './components/recently-visited.vue';
|
||||||
|
@ -24,7 +24,9 @@ export default {
|
|||||||
'workplace.allProject': 'All',
|
'workplace.allProject': 'All',
|
||||||
'workplace.loadMore': 'More',
|
'workplace.loadMore': 'More',
|
||||||
'workplace.viewMore': 'More',
|
'workplace.viewMore': 'More',
|
||||||
'workplace.contentData': 'Content Data',
|
'workplace.accessTrend': 'Access Trend',
|
||||||
|
'workplace.accessTrend.dateRange7': 'Last 7 Days',
|
||||||
|
'workplace.accessTrend.dateRange30': 'Last 30 Days',
|
||||||
'workplace.popularModule': 'Popular Module(Top10)',
|
'workplace.popularModule': 'Popular Module(Top10)',
|
||||||
'workplace.geoDistribution': 'Geo Distribution(Top10)',
|
'workplace.geoDistribution': 'Geo Distribution(Top10)',
|
||||||
'workplace.unit.pecs': 'pecs',
|
'workplace.unit.pecs': 'pecs',
|
||||||
|
@ -24,7 +24,9 @@ export default {
|
|||||||
'workplace.allProject': '所有项目',
|
'workplace.allProject': '所有项目',
|
||||||
'workplace.loadMore': '加载更多',
|
'workplace.loadMore': '加载更多',
|
||||||
'workplace.viewMore': '查看更多',
|
'workplace.viewMore': '查看更多',
|
||||||
'workplace.contentData': '内容数据',
|
'workplace.accessTrend': '访问趋势',
|
||||||
|
'workplace.accessTrend.dateRange7': '近7天',
|
||||||
|
'workplace.accessTrend.dateRange30': '近30天',
|
||||||
'workplace.popularModule': '热门模块(Top10)',
|
'workplace.popularModule': '热门模块(Top10)',
|
||||||
'workplace.geoDistribution': '访客地域分布(Top10)',
|
'workplace.geoDistribution': '访客地域分布(Top10)',
|
||||||
'workplace.unit.pecs': '个',
|
'workplace.unit.pecs': '个',
|
||||||
|
@ -21,15 +21,20 @@ import java.util.List;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import top.charles7c.cnadmin.common.model.vo.R;
|
import top.charles7c.cnadmin.common.model.vo.R;
|
||||||
|
import top.charles7c.cnadmin.common.util.validate.ValidationUtils;
|
||||||
import top.charles7c.cnadmin.monitor.annotation.Log;
|
import top.charles7c.cnadmin.monitor.annotation.Log;
|
||||||
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardAccessTrendVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardGeoDistributionVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardPopularModuleVO;
|
||||||
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
import top.charles7c.cnadmin.monitor.model.vo.DashboardTotalVO;
|
||||||
@ -58,6 +63,14 @@ public class DashboardController {
|
|||||||
return R.ok(dashboardService.getTotal());
|
return R.ok(dashboardService.getTotal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "查询访问趋势信息", description = "查询访问趋势信息")
|
||||||
|
@Parameter(name = "days", description = "日期数", example = "30", in = ParameterIn.PATH)
|
||||||
|
@GetMapping("/access/trend/{days}")
|
||||||
|
public R<List<DashboardAccessTrendVO>> listAccessTrend(@PathVariable Integer days) {
|
||||||
|
ValidationUtils.throwIf(7 != days && 30 != days, "仅支持查询近 7/30 天访问趋势信息");
|
||||||
|
return R.ok(dashboardService.listAccessTrend(days));
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "查询热门模块列表", description = "查询热门模块列表")
|
@Operation(summary = "查询热门模块列表", description = "查询热门模块列表")
|
||||||
@GetMapping("/popular/module")
|
@GetMapping("/popular/module")
|
||||||
public R<List<DashboardPopularModuleVO>> listPopularModule() {
|
public R<List<DashboardPopularModuleVO>> listPopularModule() {
|
||||||
|
Loading…
Reference in New Issue
Block a user