feat: 首页控制台新增登录区域分布

pull/95/head
李强 2023-04-21 18:51:09 +08:00
parent c656d7ac97
commit 80bc18c5fa
4 changed files with 276 additions and 1 deletions

View File

@ -198,3 +198,63 @@ class DataVViewSet(GenericViewSet):
'monthly_active': monthly_active
}
return DetailResponse(data=data, msg="获取成功")
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
def login_region(self, request):
"""
登录用户区域分布
:param request:
:return:
"""
CHINA_PROVINCES = [
{'name': '北京', 'code': '110000'},
{'name': '天津', 'code': '120000'},
{'name': '河北', 'code': '130000'},
{'name': '山西', 'code': '140000'},
{'name': '内蒙古', 'code': '150000'},
{'name': '辽宁', 'code': '210000'},
{'name': '吉林', 'code': '220000'},
{'name': '黑龙江', 'code': '230000'},
{'name': '上海', 'code': '310000'},
{'name': '江苏', 'code': '320000'},
{'name': '浙江', 'code': '330000'},
{'name': '安徽', 'code': '340000'},
{'name': '福建', 'code': '350000'},
{'name': '江西', 'code': '360000'},
{'name': '山东', 'code': '370000'},
{'name': '河南', 'code': '410000'},
{'name': '湖北', 'code': '420000'},
{'name': '湖南', 'code': '430000'},
{'name': '广东', 'code': '440000'},
{'name': '广西', 'code': '450000'},
{'name': '海南', 'code': '460000'},
{'name': '重庆', 'code': '500000'},
{'name': '四川', 'code': '510000'},
{'name': '贵州', 'code': '520000'},
{'name': '云南', 'code': '530000'},
{'name': '西藏', 'code': '540000'},
{'name': '陕西', 'code': '610000'},
{'name': '甘肃', 'code': '620000'},
{'name': '青海', 'code': '630000'},
{'name': '宁夏', 'code': '640000'},
{'name': '新疆', 'code': '650000'},
{'name': '台湾', 'code': '710000'},
{'name': '香港', 'code': '810000'},
{'name': '澳门', 'code': '820000'},
{'name': '未知区域', 'code': '000000'},
]
provinces = [x['name'] for x in CHINA_PROVINCES]
day = 30
today = datetime.datetime.today()
seven_days_ago = today - datetime.timedelta(days=day)
province_data = LoginLog.objects.filter(create_datetime__gte=seven_days_ago).values('province').annotate(
count=Count('id')).order_by('-count')
province_dict = {p: 0 for p in provinces}
for ele in province_data:
if ele.get('province') in province_dict:
province_dict[ele.get('province')] += 1
else:
province_dict['未知区域'] += ele.get('count')
data = [{'region': key, 'count': val} for key, val in province_dict.items()]
data = sorted(data, key=lambda x: x['count'], reverse=True)
return DetailResponse(data=data, msg="获取成功")

View File

@ -0,0 +1,205 @@
<template>
<el-card
class="card-view"
:style="{
backgroundColor: randomColor(),
}"
>
<div id="region" :style="{width: pxData.wpx+'px',height: pxData.hpx+'px'}"></div>
</el-card>
</template>
<script>
import { request } from '@/api/service'
export default {
sort: 7,
title: '登录区域分布',
name: 'loginRegion',
icon: 'el-icon-s-data',
description: '登录区域分布详情',
height: 28,
width: 20,
isResizable: true,
props: {
pxData: {
type: Object,
require: false,
default: () => ({
wpx: 0,
hpx: 0
})
}
},
watch: {
pxData: {
handler () {
// eslint-disable-next-line no-unused-expressions
this.myChart?.resize({ width: this.pxData.wpx, height: this.pxData.hpx })
},
immediate: true,
deep: true
}
},
data () {
this.myChart = null
return {
data: []
}
},
methods: {
initGet () {
request({
url: '/api/system/datav/login_region/'
}).then((res) => {
this.data = res.data
this.drawLine(this.data)
})
},
//
randomColor () {
const color = ['#fffff']
const ran = Math.floor(Math.random() * 4)
return color[ran]
},
drawLine () {
// domecharts
//
const xAxisData = this.data.map(item => item.region)
const seriesData = this.data.map(item => item.count)
const option = {
title: {
text: '登录区域分布',
textStyle: {
color: '#666666',
fontSize: 14,
fontWeight: '600'
},
left: 'left'
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.8)',
textStyle: {
color: '#666'
},
axisPointer: {
lineStyle: {
color: '#999',
type: 'dotted',
width: 1
}
},
formatter: params => {
const param = params[0]
return `<div style="padding: 8px;"><div style="color: #333;">${param.name}</div><div style="color: #FFA500;">${param.value} 次</div></div>`
}
},
legend: {
data: ['登录区域分布'],
textStyle: {
color: '#666',
fontSize: 12
}
},
grid: {
top: 40,
left: 40,
right: 65,
bottom: 75
},
xAxis: {
data: xAxisData,
boundaryGap: true,
axisLine: {
lineStyle: {
color: '#aaa',
width: 1
}
},
axisLabel: {
interval: '0',
maxInterval: 1,
rotate: 0,
formatter: function (value) {
return value.split('').join('\n')
},
textStyle: {
color: '#333',
fontSize: 10
}
}
},
yAxis: {
axisLine: {
lineStyle: {
color: '#aaa',
width: 1
}
},
axisLabel: {
textStyle: {
color: '#333',
fontSize: 12
}
},
splitLine: {
lineStyle: {
color: '#ddd',
type: 'dotted',
width: 1
}
}
},
series: [
{
name: '用户注册数',
type: 'bar',
data: seriesData,
barWidth: 16,
barGap: 0,
barCategoryGap: '20%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(0, 128, 255, 1)'
},
{
offset: 1,
color: 'rgba(0, 128, 255, 0.2)'
}
]
}
}
}
]
}
this.myChart.setOption(option)
}
},
mounted () {
this.myChart = this.$echarts.init(document.getElementById('region'))
this.initGet()
this.drawLine()
}
}
</script>
<style scoped lang="scss">
.card-view {
//border-radius: 10px;
color: $color-primary;
}
.el-card {
height: 100%;
}
</style>

View File

@ -262,7 +262,6 @@ export default {
},
//
containerResizedEvent: function (i, newH, newW, newHPx, newWPx) {
console.log(this.layout, 'CONTAINER RESIZED i=' + i + ', H=' + newH + ', W=' + newW + ', H(px)=' + newHPx + ', W(px)=' + newWPx)
this.layout.map(val => {
if (val.i === i) {
this.$set(this.pxData, val.i, {

View File

@ -302,6 +302,17 @@ const log = [
isResizable: true,
element: 'ver',
moved: false
},
{
i: 'loginRegion12',
x: 0,
y: 75,
w: 48,
h: 30,
config: {},
isResizable: true,
element: 'loginRegion',
moved: false
}
]
export default log