feat: ✨ 首页控制台新增登录区域分布
parent
c656d7ac97
commit
80bc18c5fa
|
@ -198,3 +198,63 @@ class DataVViewSet(GenericViewSet):
|
||||||
'monthly_active': monthly_active
|
'monthly_active': monthly_active
|
||||||
}
|
}
|
||||||
return DetailResponse(data=data, msg="获取成功")
|
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="获取成功")
|
||||||
|
|
|
@ -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 () {
|
||||||
|
// 基于准备好的dom,初始化echarts实例
|
||||||
|
// 绘制图表
|
||||||
|
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>
|
|
@ -262,7 +262,6 @@ export default {
|
||||||
},
|
},
|
||||||
// 设置实际的宽度和高度
|
// 设置实际的宽度和高度
|
||||||
containerResizedEvent: function (i, newH, newW, newHPx, newWPx) {
|
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 => {
|
this.layout.map(val => {
|
||||||
if (val.i === i) {
|
if (val.i === i) {
|
||||||
this.$set(this.pxData, val.i, {
|
this.$set(this.pxData, val.i, {
|
||||||
|
|
|
@ -302,6 +302,17 @@ const log = [
|
||||||
isResizable: true,
|
isResizable: true,
|
||||||
element: 'ver',
|
element: 'ver',
|
||||||
moved: false
|
moved: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
i: 'loginRegion12',
|
||||||
|
x: 0,
|
||||||
|
y: 75,
|
||||||
|
w: 48,
|
||||||
|
h: 30,
|
||||||
|
config: {},
|
||||||
|
isResizable: true,
|
||||||
|
element: 'loginRegion',
|
||||||
|
moved: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
export default log
|
export default log
|
||||||
|
|
Loading…
Reference in New Issue