mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-10-31 22:57:15 +08:00 
			
		
		
		
	feat: 新增分析页
This commit is contained in:
		| @@ -14,3 +14,23 @@ export function listDashboardAccessTrend(days: number) { | ||||
| export function listDashboardNotice() { | ||||
|   return http.get<T.DashboardNoticeResp[]>(`${BASE_URL}/notice`) | ||||
| } | ||||
|  | ||||
| /** @desc 查询访问时段分析 */ | ||||
| export function getAnalysisTimeslot() { | ||||
|   return http.get<T.DashboardChartCommonResp[]>(`${BASE_URL}/analysis/timeslot`) | ||||
| } | ||||
|  | ||||
| /** @desc 查询模块分析 */ | ||||
| export function getAnalysisModule() { | ||||
|   return http.get<T.DashboardChartCommonResp[]>(`${BASE_URL}/analysis/module`) | ||||
| } | ||||
|  | ||||
| /** @desc 查询终端分析 */ | ||||
| export function getAnalysisOs() { | ||||
|   return http.get<T.DashboardChartCommonResp[]>(`${BASE_URL}/analysis/os`) | ||||
| } | ||||
|  | ||||
| /** @desc 查询浏览器分析 */ | ||||
| export function getAnalysisBrowser() { | ||||
|   return http.get<T.DashboardChartCommonResp[]>(`${BASE_URL}/analysis/browser`) | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,12 @@ export interface DashboardAccessTrendResp { | ||||
|   ipCount: number | ||||
| } | ||||
|  | ||||
| /** 仪表盘图表类型 */ | ||||
| export interface DashboardChartCommonResp { | ||||
|   name: string | ||||
|   value: number | ||||
| } | ||||
|  | ||||
| /** 仪表盘公告类型 */ | ||||
| export interface DashboardNoticeResp { | ||||
|   id: number | ||||
|   | ||||
							
								
								
									
										41
									
								
								src/components/Chart/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/components/Chart/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| <template> | ||||
|   <VCharts | ||||
|     v-if="renderChart" | ||||
|     :option="option" | ||||
|     :autoresize="autoResize" | ||||
|     :style="{ width, height }" | ||||
|   /> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { nextTick, ref } from 'vue' | ||||
| import VCharts from 'vue-echarts' | ||||
|  | ||||
| defineProps({ | ||||
|   option: { | ||||
|     type: Object, | ||||
|     default() { | ||||
|       return {} | ||||
|     } | ||||
|   }, | ||||
|   autoResize: { | ||||
|     type: Boolean, | ||||
|     default: true | ||||
|   }, | ||||
|   width: { | ||||
|     type: String, | ||||
|     default: '100%' | ||||
|   }, | ||||
|   height: { | ||||
|     type: String, | ||||
|     default: '100%' | ||||
|   } | ||||
| }) | ||||
| const renderChart = ref(false) | ||||
| // wait container expand | ||||
| nextTick(() => { | ||||
|   renderChart.value = true | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"></style> | ||||
| @@ -188,6 +188,7 @@ | ||||
|   padding: $margin; | ||||
|   box-sizing: border-box; | ||||
|   overflow-y: auto; | ||||
|   overflow-x: hidden; | ||||
| } | ||||
|  | ||||
| // 表格页面 | ||||
| @@ -311,18 +312,11 @@ | ||||
|  | ||||
| // 通用卡片 | ||||
| .general-card { | ||||
|   height: 100%; | ||||
|   overflow-y: auto; | ||||
|   border: none; | ||||
|   & > .arco-card-header { | ||||
|     height: auto; | ||||
|     padding: $padding; | ||||
|     border: none; | ||||
|     .arco-card-header-title { | ||||
|       color: var(--color-text-1); | ||||
|       font-size: 18px; | ||||
|       font-weight: 500; | ||||
|       line-height: 1.5; | ||||
|     } | ||||
|   } | ||||
|   & > .arco-card-body { | ||||
|     padding: 0 $padding $padding $padding; | ||||
|   | ||||
							
								
								
									
										1
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -8,6 +8,7 @@ export {} | ||||
| declare module 'vue' { | ||||
|   export interface GlobalComponents { | ||||
|     Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default'] | ||||
|     Chart: typeof import('./../components/Chart/index.vue')['default'] | ||||
|     CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default'] | ||||
|     CronModel: typeof import('./../components/GenCron/CronModel/index.vue')['default'] | ||||
|     DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default'] | ||||
|   | ||||
							
								
								
									
										204
									
								
								src/views/dashboard/analysis/components/AccessTimeslot.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								src/views/dashboard/analysis/components/AccessTimeslot.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,204 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading" style="width: 100%"> | ||||
|     <a-card title="访问时段分析" class="general-card" :header-style="{ paddingBottom: '16px' }"> | ||||
|       <Chart style="width: 100%; height: 370px" :option="option" /> | ||||
|     </a-card> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { graphic } from 'echarts' | ||||
| import { useChart } from '@/hooks' | ||||
| import { type DashboardChartCommonResp, getAnalysisTimeslot as getData } from '@/apis/common' | ||||
|  | ||||
| // 提示框 | ||||
| const tooltipItemsHtmlString = (items) => { | ||||
|   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 xAxis = ref<string[]>([]) | ||||
| const dataList = ref<number[]>([]) | ||||
| const { option } = useChart((isDark) => { | ||||
|   return { | ||||
|     grid: { | ||||
|       left: '40', | ||||
|       right: 0, | ||||
|       top: '20', | ||||
|       bottom: '100' | ||||
|     }, | ||||
|     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: { | ||||
|         lineStyle: { | ||||
|           color: isDark ? '#3f3f3f' : '#A9AEB8' | ||||
|         } | ||||
|       }, | ||||
|       axisTick: { | ||||
|         show: true, | ||||
|         alignWithLabel: true, | ||||
|         lineStyle: { | ||||
|           color: '#86909C' | ||||
|         }, | ||||
|         interval(idx: number) { | ||||
|           if (idx === 0) return false | ||||
|           if (idx === xAxis.value.length - 1) return false | ||||
|           return true | ||||
|         } | ||||
|       }, | ||||
|       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 | ||||
|           if (value >= 1000) { | ||||
|             return `${value / 1000}k` | ||||
|           } | ||||
|           return `${value}` | ||||
|         } | ||||
|       }, | ||||
|       axisLine: { | ||||
|         show: false | ||||
|       }, | ||||
|       splitLine: { | ||||
|         lineStyle: { | ||||
|           type: 'dashed', | ||||
|           color: isDark ? '#3F3F3F' : '#E5E8EF' | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     tooltip: { | ||||
|       show: true, | ||||
|       trigger: 'axis', | ||||
|       formatter(params) { | ||||
|         const [firstElement] = params | ||||
|         return `<div> | ||||
|             <p class="tooltip-title">${firstElement.axisValueLabel}</p> | ||||
|             ${tooltipItemsHtmlString(params)} | ||||
|           </div>` | ||||
|       }, | ||||
|       className: 'echarts-tooltip-diy' | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         name: '浏览量(PV)', | ||||
|         data: dataList.value, | ||||
|         type: 'line', | ||||
|         smooth: true, | ||||
|         showSymbol: false, | ||||
|         color: '#246EFF', | ||||
|         emphasis: { | ||||
|           focus: 'series', | ||||
|           itemStyle: { | ||||
|             borderWidth: 2, | ||||
|             borderColor: '#E0E3FF' | ||||
|           } | ||||
|         }, | ||||
|         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)' | ||||
|             } | ||||
|           ]) | ||||
|         } | ||||
|       } | ||||
|     ], | ||||
|     dataZoom: [ | ||||
|       { | ||||
|         bottom: 40, | ||||
|         type: 'slider', | ||||
|         left: 40, | ||||
|         right: 14, | ||||
|         height: 14, | ||||
|         borderColor: 'transparent', | ||||
|         handleIcon: | ||||
|           'image://http://p3-armor.byteimg.com/tos-cn-i-49unhts6dw/1ee5a8c6142b2bcf47d2a9f084096447.svg~tplv-49unhts6dw-image.image', | ||||
|         handleSize: '20', | ||||
|         handleStyle: { | ||||
|           shadowColor: 'rgba(0, 0, 0, 0.2)', | ||||
|           shadowBlur: 4 | ||||
|         }, | ||||
|         brushSelect: false, | ||||
|         backgroundColor: isDark ? '#313132' : '#F2F3F5' | ||||
|       }, | ||||
|       { | ||||
|         type: 'inside', | ||||
|         start: 0, | ||||
|         end: 100, | ||||
|         zoomOnMouseWheel: false | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const loading = ref(false) | ||||
| // 查询图表数据 | ||||
| const getChartData = async () => { | ||||
|   try { | ||||
|     loading.value = true | ||||
|     const { data } = await getData() | ||||
|     data.forEach((item: DashboardChartCommonResp) => { | ||||
|       xAxis.value.push(item.name) | ||||
|       dataList.value.push(item.value) | ||||
|     }) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getChartData() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| :deep(.arco-card-body) { | ||||
|   padding-bottom: 0; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										218
									
								
								src/views/dashboard/analysis/components/AccessTrend.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								src/views/dashboard/analysis/components/AccessTrend.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,218 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading" style="width: 100%"> | ||||
|     <a-card title="访问趋势" class="general-card"> | ||||
|       <template #extra> | ||||
|         <a-radio-group v-model:model-value="dateRange" type="button" size="small" @change="onChange as any"> | ||||
|           <a-radio :value="7">近7天</a-radio> | ||||
|           <a-radio :value="30">近30天</a-radio> | ||||
|         </a-radio-group> | ||||
|       </template> | ||||
|       <Chart :option="option" :style="{ height: '326px' }" /> | ||||
|     </a-card> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { graphic } from 'echarts' | ||||
| import { type DashboardAccessTrendResp, listDashboardAccessTrend } from '@/apis' | ||||
| import { useChart } from '@/hooks' | ||||
|  | ||||
| // 提示框 | ||||
| const tooltipItemsHtmlString = (items) => { | ||||
|   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 xData = ref<string[]>([]) | ||||
| const pvStatisticsData = ref<number[]>([]) | ||||
| const ipStatisticsData = ref<number[]>([]) | ||||
| const { option } = useChart((isDark) => { | ||||
|   return { | ||||
|     grid: { | ||||
|       left: '38', | ||||
|       right: '0', | ||||
|       top: '10', | ||||
|       bottom: '50' | ||||
|     }, | ||||
|     legend: { | ||||
|       bottom: -3, | ||||
|       icon: 'circle', | ||||
|       textStyle: { | ||||
|         color: '#4E5969' | ||||
|       } | ||||
|     }, | ||||
|     xAxis: { | ||||
|       type: 'category', | ||||
|       offset: 2, | ||||
|       data: xData.value, | ||||
|       boundaryGap: false, | ||||
|       axisLabel: { | ||||
|         color: '#4E5969', | ||||
|         formatter(value: number, idx: number) { | ||||
|           if (idx === 0) return '' | ||||
|           if (idx === xData.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 !== xData.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 | ||||
|           if (value >= 1000) { | ||||
|             return `${value / 1000}k` | ||||
|           } | ||||
|           return `${value}` | ||||
|         } | ||||
|       }, | ||||
|       axisLine: { | ||||
|         show: false | ||||
|       }, | ||||
|       splitLine: { | ||||
|         lineStyle: { | ||||
|           type: 'dashed', | ||||
|           color: isDark ? '#3F3F3F' : '#E5E8EF' | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     tooltip: { | ||||
|       show: true, | ||||
|       trigger: 'axis', | ||||
|       formatter(params) { | ||||
|         const [firstElement] = params | ||||
|         return `<div> | ||||
|             <p class="tooltip-title">${firstElement.axisValueLabel}</p> | ||||
|             ${tooltipItemsHtmlString(params)} | ||||
|           </div>` | ||||
|       }, | ||||
|       className: 'echarts-tooltip-diy' | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         name: '浏览量(PV)', | ||||
|         data: pvStatisticsData.value, | ||||
|         type: 'line', | ||||
|         smooth: true, | ||||
|         showSymbol: false, | ||||
|         color: '#246EFF', | ||||
|         symbol: 'circle', | ||||
|         symbolSize: 10, | ||||
|         emphasis: { | ||||
|           focus: 'series', | ||||
|           itemStyle: { | ||||
|             borderWidth: 2, | ||||
|             borderColor: '#E0E3FF' | ||||
|           } | ||||
|         }, | ||||
|         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)' | ||||
|             } | ||||
|           ]) | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         name: 'IP数', | ||||
|         data: ipStatisticsData.value, | ||||
|         type: 'line', | ||||
|         smooth: true, | ||||
|         showSymbol: false, | ||||
|         color: '#00B2FF', | ||||
|         symbol: 'circle', | ||||
|         symbolSize: 10, | ||||
|         emphasis: { | ||||
|           focus: 'series', | ||||
|           itemStyle: { | ||||
|             borderWidth: 2, | ||||
|             borderColor: '#E2F2FF' | ||||
|           } | ||||
|         }, | ||||
|         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 loading = ref(false) | ||||
| const dateRange = ref(30) | ||||
| // 查询图表数据 | ||||
| const getChartData = async (days: number) => { | ||||
|   try { | ||||
|     loading.value = true | ||||
|     xData.value = [] | ||||
|     pvStatisticsData.value = [] | ||||
|     ipStatisticsData.value = [] | ||||
|     const { data: chartData } = await listDashboardAccessTrend(days) | ||||
|     chartData.forEach((el: DashboardAccessTrendResp) => { | ||||
|       xData.value.unshift(el.date) | ||||
|       pvStatisticsData.value.unshift(el.pvCount) | ||||
|       ipStatisticsData.value.unshift(el.ipCount) | ||||
|     }) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 切换 | ||||
| const onChange = (days: number) => { | ||||
|   getChartData(days) | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getChartData(30) | ||||
| }) | ||||
| </script> | ||||
							
								
								
									
										82
									
								
								src/views/dashboard/analysis/components/BrowserItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/views/dashboard/analysis/components/BrowserItem.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading" style="width: 100%"> | ||||
|     <a-card class="general-card" title="浏览器分析" :header-style="{ paddingBottom: '12px' }"> | ||||
|       <div class="chart"> | ||||
|         <Chart v-if="!loading" style="height: 210px" :option="option" /> | ||||
|       </div> | ||||
|     </a-card> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { useChart } from '@/hooks' | ||||
| import { type DashboardChartCommonResp, getAnalysisBrowser as getData } from '@/apis/common' | ||||
|  | ||||
| const xAxis = ref<string[]>([]) | ||||
| const dataList = ref([]) | ||||
| const { option } = useChart((isDark) => { | ||||
|   return { | ||||
|     legend: { | ||||
|       bottom: 'center', | ||||
|       data: xAxis.value, | ||||
|       bottom: 0, | ||||
|       icon: 'circle', | ||||
|       itemWidth: 8, | ||||
|       textStyle: { | ||||
|         color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969' | ||||
|       }, | ||||
|       itemStyle: { | ||||
|         borderWidth: 0 | ||||
|       } | ||||
|     }, | ||||
|     tooltip: { | ||||
|       show: true, | ||||
|       trigger: 'item' | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         type: 'pie', | ||||
|         radius: ['50%', '70%'], | ||||
|         center: ['50%', '45%'], | ||||
|         label: { | ||||
|           formatter: '{d}% ', | ||||
|           color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969' | ||||
|         }, | ||||
|         itemStyle: { | ||||
|           borderColor: isDark ? '#000' : '#fff', | ||||
|           borderWidth: 1 | ||||
|         }, | ||||
|         data: dataList.value | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const loading = ref(false) | ||||
| const colors = ['#249EFF', '#846BCE', '#21CCFF', '#0E42D2', '#86DF6C'] | ||||
| // 查询图表数据 | ||||
| const getChartData = async () => { | ||||
|   try { | ||||
|     loading.value = true | ||||
|     const { data } = await getData() | ||||
|     data.forEach((item: DashboardChartCommonResp, index) => { | ||||
|       xAxis.value.push(item.name) | ||||
|       dataList.value.push({ | ||||
|         ...item, | ||||
|         itemStyle: { | ||||
|           color: data.length > 1 && index === data.length - 1 ? colors[colors.length - 1] : colors[index] | ||||
|         } | ||||
|       }) | ||||
|     }) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getChartData() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
| </style> | ||||
							
								
								
									
										82
									
								
								src/views/dashboard/analysis/components/ModuleItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/views/dashboard/analysis/components/ModuleItem.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading" style="width: 100%"> | ||||
|     <a-card class="general-card" title="模块分析" :header-style="{ paddingBottom: '12px' }"> | ||||
|       <div class="chart"> | ||||
|         <Chart v-if="!loading" style="height: 210px" :option="option" /> | ||||
|       </div> | ||||
|     </a-card> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { useChart } from '@/hooks' | ||||
| import { type DashboardChartCommonResp, getAnalysisModule as getData } from '@/apis/common' | ||||
|  | ||||
| const xAxis = ref<string[]>([]) | ||||
| const dataList = ref([]) | ||||
| const { option } = useChart((isDark) => { | ||||
|   return { | ||||
|     legend: { | ||||
|       bottom: 'center', | ||||
|       data: xAxis.value, | ||||
|       bottom: 0, | ||||
|       icon: 'circle', | ||||
|       itemWidth: 8, | ||||
|       textStyle: { | ||||
|         color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969' | ||||
|       }, | ||||
|       itemStyle: { | ||||
|         borderWidth: 0 | ||||
|       } | ||||
|     }, | ||||
|     tooltip: { | ||||
|       show: true, | ||||
|       trigger: 'item' | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         type: 'pie', | ||||
|         radius: ['50%', '70%'], | ||||
|         center: ['50%', '45%'], | ||||
|         label: { | ||||
|           formatter: '{d}% ', | ||||
|           color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969' | ||||
|         }, | ||||
|         itemStyle: { | ||||
|           borderColor: isDark ? '#000' : '#fff', | ||||
|           borderWidth: 1 | ||||
|         }, | ||||
|         data: dataList.value | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const loading = ref(false) | ||||
| const colors = ['#249EFF', '#846BCE', '#21CCFF', '#0E42D2', '#86DF6C'] | ||||
| // 查询图表数据 | ||||
| const getChartData = async () => { | ||||
|   try { | ||||
|     loading.value = true | ||||
|     const { data } = await getData() | ||||
|     data.forEach((item: DashboardChartCommonResp, index) => { | ||||
|       xAxis.value.push(item.name) | ||||
|       dataList.value.push({ | ||||
|         ...item, | ||||
|         itemStyle: { | ||||
|           color: data.length > 1 && index === data.length - 1 ? colors[colors.length - 1] : colors[index] | ||||
|         } | ||||
|       }) | ||||
|     }) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getChartData() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
| </style> | ||||
							
								
								
									
										82
									
								
								src/views/dashboard/analysis/components/OsItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/views/dashboard/analysis/components/OsItem.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| <template> | ||||
|   <a-spin :loading="loading" style="width: 100%"> | ||||
|     <a-card class="general-card" title="终端分析" :header-style="{ paddingBottom: '12px' }"> | ||||
|       <div class="chart"> | ||||
|         <Chart v-if="!loading" style="height: 210px" :option="option" /> | ||||
|       </div> | ||||
|     </a-card> | ||||
|   </a-spin> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { useChart } from '@/hooks' | ||||
| import { type DashboardChartCommonResp, getAnalysisOs as getData } from '@/apis/common' | ||||
|  | ||||
| const xAxis = ref<string[]>([]) | ||||
| const dataList = ref([]) | ||||
| const { option } = useChart((isDark) => { | ||||
|   return { | ||||
|     legend: { | ||||
|       bottom: 'center', | ||||
|       data: xAxis.value, | ||||
|       bottom: 0, | ||||
|       icon: 'circle', | ||||
|       itemWidth: 8, | ||||
|       textStyle: { | ||||
|         color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969' | ||||
|       }, | ||||
|       itemStyle: { | ||||
|         borderWidth: 0 | ||||
|       } | ||||
|     }, | ||||
|     tooltip: { | ||||
|       show: true, | ||||
|       trigger: 'item' | ||||
|     }, | ||||
|     series: [ | ||||
|       { | ||||
|         type: 'pie', | ||||
|         radius: ['50%', '70%'], | ||||
|         center: ['50%', '45%'], | ||||
|         label: { | ||||
|           formatter: '{d}% ', | ||||
|           color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969' | ||||
|         }, | ||||
|         itemStyle: { | ||||
|           borderColor: isDark ? '#000' : '#fff', | ||||
|           borderWidth: 1 | ||||
|         }, | ||||
|         data: dataList.value | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const loading = ref(false) | ||||
| const colors = ['#249EFF', '#846BCE', '#21CCFF', '#0E42D2', '#86DF6C'] | ||||
| // 查询图表数据 | ||||
| const getChartData = async () => { | ||||
|   try { | ||||
|     loading.value = true | ||||
|     const { data } = await getData() | ||||
|     data.forEach((item: DashboardChartCommonResp, index) => { | ||||
|       xAxis.value.push(item.name) | ||||
|       dataList.value.push({ | ||||
|         ...item, | ||||
|         itemStyle: { | ||||
|           color: data.length > 1 && index === data.length - 1 ? colors[colors.length - 1] : colors[index] | ||||
|         } | ||||
|       }) | ||||
|     }) | ||||
|   } finally { | ||||
|     loading.value = false | ||||
|   } | ||||
| } | ||||
|  | ||||
| onMounted(() => { | ||||
|   getChartData() | ||||
| }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
| </style> | ||||
| @@ -1,10 +1,36 @@ | ||||
| <template> | ||||
|   <div id="home" class="gi_page home"> | ||||
|     分析页面开发中... | ||||
|   <div class="gi_page container"> | ||||
|     <a-space direction="vertical" :size="16" fill> | ||||
|       <div> | ||||
|         <AccessTrend /> | ||||
|       </div> | ||||
|       <div> | ||||
|         <a-grid :cols="24" :col-gap="16" :row-gap="16"> | ||||
|           <a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 8, xxl: 8 }"> | ||||
|             <ModuleItem /> | ||||
|           </a-grid-item> | ||||
|           <a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 8, xxl: 8 }"> | ||||
|             <OsItem /> | ||||
|           </a-grid-item> | ||||
|           <a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 8, xxl: 8 }"> | ||||
|             <BrowserItem /> | ||||
|           </a-grid-item> | ||||
|         </a-grid> | ||||
|       </div> | ||||
|       <div> | ||||
|         <AccessTimeslot /> | ||||
|       </div> | ||||
|     </a-space> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import AccessTrend from './components/AccessTrend.vue' | ||||
| import ModuleItem from './components/ModuleItem.vue' | ||||
| import OsItem from './components/OsItem.vue' | ||||
| import BrowserItem from './components/BrowserItem.vue' | ||||
| import AccessTimeslot from './components/AccessTimeslot.vue' | ||||
|  | ||||
| defineOptions({ name: 'Analysis' }) | ||||
| </script> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user