This commit is contained in:
hyx
2024-03-26 10:24:27 +08:00
26 changed files with 1260 additions and 118 deletions

View File

@@ -84,7 +84,7 @@ export default {
username: '',
pin: '',
language: '',
licenseStatus: 1,
licenseStatus: 0,
licenseStatusErrMsg: '',
downloadC2vUrl: api.downloadLicenseC2v,
supportID: ''
@@ -184,7 +184,7 @@ export default {
},
uploadSuccess (response) {
this.$message.success('Success')
this.licenseStatus = 1
this.licenseStatus = 0
},
uploadError (error) {
let errorMsg
@@ -193,6 +193,7 @@ export default {
} else {
errorMsg = 'error'
}
this.licenseStatus = 1
this.$message.error('Upload failed: ' + errorMsg)
},
checkLicenseStatus () {

View File

@@ -224,7 +224,7 @@
.my-popper-class {
width: auto !important;
min-width: 120px !important;
min-width: 125px !important;
padding: 8px 10px !important;
}
.el-input__wrapper.is-focus {

View File

@@ -142,6 +142,9 @@
// border: 1px solid #dcdfe6;
// border-radius: 2px 0 0 2px;
//}
.el-select__prefix {
line-height:30px;
}
.el-select__prefix, .el-input__prefix {
left: 0;
background-color: #f5f7fa;

View File

@@ -24,7 +24,7 @@
border-radius: 4px;
height: calc(100% - 34px);
.subscriber-map {
.subscriber-map, .entity-subscriber-map {
height: 100%;
width: calc(100% - 290px);
@@ -118,6 +118,9 @@
}
}
}
.entity-subscriber-map {
width: 100%;
}
.panel-chart__no-data {
height: calc(100% - 46px);
}

View File

@@ -215,7 +215,7 @@
display: flex;
flex-direction: column;
height: 100%;
overflow-y: hidden;
.add-app__header {
display: flex;
justify-content: space-between;
@@ -232,7 +232,12 @@
.header__operations {
display: flex;
align-items: center;
.no-records {
background: #21B4ED;
color: #FFFFFF !important;
opacity: 0.6;
cursor: not-allowed !important;
}
.header__operation {
width: 80px;
height: 30px;

View File

@@ -136,7 +136,7 @@
display: flex;
//flex-direction: column;
align-items: center;
padding: 0 0 0 30px;
padding: 0 0 0 20px;
.overview-left-span {
font-size: 16px;

View File

@@ -166,8 +166,11 @@
min-width: 90px !important;
}
}
.row__content-sent {
margin-right: 39px;
}
.row__content-accept {
margin-left: 39px;
//margin-left: 39px;
}
.row__charts-msg {
width: auto;
@@ -183,6 +186,11 @@
//padding-left: 5px;
}
}
.row__contents-subscriber {
display: flex;
width: calc(100% - 688px); // 分辨率过小时换行
flex-wrap: wrap;
}
.row__charts {
width:80px;
height:20px;

View File

@@ -76,8 +76,8 @@
font-size: 16px;
padding-bottom: 3px;
color: #333333;
.cn-entity__header-title {
margin-right: 10px;
.cn-entity__header-icon {
margin-left: 10px;
}
.entity-related-entity {
font-size: 12px;
@@ -222,7 +222,12 @@
}
}
.show-detail__block {
display: flex;
align-items: center;
}
.new-show-detail {
height: 52px;
flex-shrink: 0;
padding: 0 30px;
font-size: 12px;

View File

@@ -36,11 +36,11 @@ $--border-color: #E2E5EC;
@import "common/cover-element-plus"; // 覆盖新版elemen-plus样式
.dark {
$--theme: dark;
@import './themes/theme-dark.scss'; // 加载主题变量
@import './components/index'; // 加载cn组件样式
@import './common/index.scss'; // 加载通用样式
.fixed-button:hover {
background: #40474b !important;
}
//$--theme: dark;
//@import './themes/theme-dark.scss'; // 加载主题变量
//@import './components/index'; // 加载cn组件样式
//@import './common/index.scss'; // 加载通用样式
//.fixed-button:hover {
// background: #40474b !important;
//}
}

View File

@@ -43,10 +43,10 @@
</el-select>
</template>
<div v-else-if="editObject.config.timeConfig.type === 'last' || editObject.config.timeConfig.type === 'previous'" style="display: flex;">
<el-input v-model.number="editObject.config.timeConfig.offset" size="small" class="el-input-single" placeholder=" ">
<el-input-number v-model.number="editObject.config.timeConfig.offset" size="small" class="el-input-single" placeholder=" ">
<template #prepend><i @click="timeOffsetHandle('m')" class="cn-icon cn-icon-a-"></i></template>
<template #append><i @click="timeOffsetHandle('p')" class="cn-icon cn-icon-a-1"></i></template>
</el-input>
</el-input-number>
<el-select id="reportBoxTimeUnitSelect"
v-model="editObject.config.timeConfig.unit"
class="right-box__select right-box__select-single"
@@ -107,14 +107,14 @@
</div>
<div class="enable-tabs-daily" v-if="scheduleType === scheduleTypeList[0].value">
<div class="enable-tabs-custom">{{$t('report.customEvery')}}</div>
<el-input v-model.number="editObject.config.schedulerConfig.interval" size="small" placeholder=" " style="margin-top: 0.3125rem;">
<el-input-number v-model.number="editObject.config.schedulerConfig.interval" size="small" placeholder=" " style="margin-top: 0.3125rem;">
<template #append>{{$t('report.day')}}</template>
</el-input>
</el-input-number>
</div>
<div class="enable-tabs-weekly" v-else-if="scheduleType === scheduleTypeList[1].value">
<!-- 每隔几周暂时隐藏 -->
<!-- <div class="enable-tabs-custom">{{$t('report.customEvery')}}</div>
<el-input v-model="editObject.config.schedulerConfig.interval" size="small" placeholder="Please input">
<el-input-number v-model="editObject.config.schedulerConfig.interval" size="small" placeholder="Please input">
<template #append>{{$t('report.week')}}</template>
</el-input>-->
<el-checkbox-group v-model="editObject.config.schedulerConfig.weekDates" style="margin-top: 0.3125rem">
@@ -132,9 +132,9 @@
<!-- 自定义月循环 -->
<template v-if="monthIsCycle">
<div class="enable-tabs-custom">{{$t('report.customEvery')}}</div>
<el-input v-model="editObject.config.schedulerConfig.interval" size="small" placeholder=" " style="margin-top: 0.3125rem;">
<el-input-number v-model="editObject.config.schedulerConfig.interval" size="small" placeholder=" " style="margin-top: 0.3125rem;">
<template #append>{{$t('report.month')}}</template>
</el-input>
</el-input-number>
</template>
<!-- 自定义月非循环 -->
<template v-else>
@@ -229,13 +229,13 @@
</el-form-item>
<!-- 参数 -->
<el-form-item :label="$t('config.operationlog.params')" prop="categoryParams" v-if="editObject.categoryParams && editObject.categoryParams.length > 0">
<!-- <el-input v-model="param.value" placeholder=" " v-for="(param, index) in editObject.categoryParams" :key="index" size="small" style="vertical-align: unset;" :disabled="!!editObject.id">
<!-- <el-input-number v-model="param.value" placeholder=" " v-for="(param, index) in editObject.categoryParams" :key="index" size="small" style="vertical-align: unset;" :disabled="!!editObject.id">
<template #prepend>{{param.key}}</template>
</el-input>-->
</el-input-number>-->
<template v-for="(param, index) in editObject.categoryParams" :key="index">
<el-input v-if="param.labelType === 'input'" v-model="param.value" placeholder=" " size="small" style="vertical-align: unset;" :disabled="!!editObject.id">
<el-input-number v-if="param.labelType === 'input'" v-model="param.value" placeholder=" " size="small" style="vertical-align: unset;" :disabled="!!editObject.id">
<template #prepend>{{param.key}}</template>
</el-input>
</el-input-number>
<el-select v-model="param.value"
v-else
class="right-box__select right-box__select--param"
@@ -332,7 +332,7 @@ export default {
}
const startTimeValidator = (rule, value, callback) => {
const form = proxy.$refs.reportForm
if (form.model.config.endTime) {
if (form.model && form.model.config && form.model.config.endTime) {
form.validateField('config.endTime', () => null)
}
callback()
@@ -414,14 +414,7 @@ export default {
scheduleChecked (n) {
this.editObject.config.isScheduler = n ? 1 : 0
this.cleanScheduleConfig()
this.$nextTick(() => {
let datePrefixIcon = document.getElementsByClassName('el-input__prefix-inner')
if(datePrefixIcon && datePrefixIcon.length > 0) {
Array.prototype.forEach.call(datePrefixIcon, function (element) {
element.innerHTML = '<i class="el-input__icon cn-icon cn-icon-shijian"></i>'
})
}
})
this.initDateCalendarPreIcon()
},
monthScheduleType (n) {
this.cleanScheduleConfig()
@@ -530,16 +523,19 @@ export default {
}
},
mounted() {
this.$nextTick(() => {
let datePrefixIcon = document.getElementsByClassName('el-input__prefix-inner')
if(datePrefixIcon && datePrefixIcon.length > 0) {
Array.prototype.forEach.call(datePrefixIcon, function (element) {
element.innerHTML = '<i class="el-input__icon cn-icon cn-icon-shijian"></i>'
})
}
})
this.initDateCalendarPreIcon()
},
methods: {
initDateCalendarPreIcon() {
this.$nextTick(() => {
let datePrefixIcon = document.getElementsByClassName('el-input__prefix-inner')
if(datePrefixIcon && datePrefixIcon.length > 0) {
Array.prototype.forEach.call(datePrefixIcon, function (element) {
element.innerHTML = '<i class="el-input__icon cn-icon cn-icon-shijian"></i>'
})
}
})
},
loadParamOptions () {
if (_.isArray(this.editObject.categoryParams) && !_.isEmpty(this.editObject.categoryParams)) {
this.editObject.categoryParams.forEach(param => {
@@ -587,6 +583,7 @@ export default {
if (val === 'customize') {
this.scheduleChecked = false
}
this.initDateCalendarPreIcon()
},
scheduleTypeChange (val) {
this.scheduleType = val

View File

@@ -20,7 +20,7 @@ export default {
axios.get(relationshipUrlOne, { params: this.getQueryParams(DEFAULT_TIME_FILTER_RANGE.entity.relatedEntity) }).then(response => {
if (response.status === 200) {
const relationshipDataOne = []
if (response.data.data.result.length > 0) {
if (response.data.data.result && response.data.data.result.length > 0) {
response.data.data.result.forEach(item => {
relationshipDataOne.push({ value: item, show: true })
})
@@ -36,7 +36,7 @@ export default {
axios.get(relationshipUrlTow, { params: this.getQueryParams(DEFAULT_TIME_FILTER_RANGE.entity.relatedEntity) }).then(response => {
if (response.status === 200) {
const relationshipDataTwo = []
if (response.data.data.result.length > 0) {
if (response.data.data.result && response.data.data.result.length > 0) {
response.data.data.result.forEach(item => {
relationshipDataTwo.push({ value: item, show: true })
})

View File

@@ -455,9 +455,10 @@ if (openMock) {
const result = {
pageNo: 1,
pageSize: 10,
total: 3,
total: 4,
list: [
{ entityValue: '192.168.12.34', entityType: 'ip' },
{ entityValue: 'test123987', entityType: 'subscriber_id' },
{ entityValue: '172.217.24.66', entityType: 'ip' },
{ entityValue: 'gdbzkz.com', entityType: 'domain' },
{ entityValue: 'qqvideo', entityType: 'app' }
]
@@ -528,6 +529,17 @@ if (openMock) {
}
break
}
case ('subscriber_id'): {
result = {
phone_number: '1568912455',
imei: 'r45ty5454fh',
imsi: 'eere2545t',
apn: 'xxx站点',
subscriber_longitude: 116.370042,
subscriber_latitude: 39.944283
}
break
}
}
return {
@@ -719,7 +731,7 @@ if (openMock) {
}
})
Mock.mock(new RegExp(`${urlAndVersion}/entity/explorer/overview/active.*`), 'get', function (requestObj) {
const data = { domainCount: 755, ipCount: 8373, appCount: 263 }
const data = { domainCount: 755, ipCount: 8373, appCount: 263, subscriberCount: 263 }
return {
msg: 'success',
@@ -737,7 +749,7 @@ if (openMock) {
}
})
Mock.mock(new RegExp(`${urlAndVersion}/entity/explorer/overview/total.*`), 'get', function (requestObj) {
const data = { domainCount: 7686274, ipCount: 2169957, appCount: 856 }
const data = { domainCount: 7686274, ipCount: 2169957, appCount: 856, subscriberCount: 856 }
return {
msg: 'success',
@@ -745,6 +757,49 @@ if (openMock) {
data: data
}
})
Mock.mock(new RegExp(`${urlAndVersion}/entity/explorer/detail/traffic/throughput/subscriber.*`), 'get', function (requestObj) {
const arr = [{ legend: 'sentRate' }, { legend: 'rate' }, { legend: 'receivedRate' }]
const endTime = JSON.parse(getQuery(requestObj.url).endTime)
let step = 0
arr.forEach(item => {
let startTime = JSON.parse(getQuery(requestObj.url).startTime)
step = (endTime - startTime) / 60 // 每条数据的时间间隔
const max = 2975
const min = 0
const values = []
for (let i = 0; i < step; i++) {
const random = Math.floor(Math.random() * (max - min) + min)
values.push([startTime, random])
startTime += step
}
item.values = values
item.aggregation = { first: 0, last: 0, avg: '602.64', p50: '0.0', p90: '1852.0', max: '5256.0' }
})
const data = {
result: arr,
resultType: 'matrix'
}
return {
msg: 'success',
code: 200,
data: data
}
})
Mock.mock(new RegExp(`${urlAndVersion}/entity/explorer/detail/subscriber/relate.*`), 'get', function (requestObj) {
const result = {
total: 5,
result: ['appName1', 'appName2', 'appName3', 'appName4', 'appName5', 'appName6', 'appName7', 'appName1', 'appName2', 'appName3', 'appName4', 'appName5', 'appName6', 'appName7']
}
return {
msg: 'success',
code: 200,
data: result
}
})
}
const getQuery = (url) => {
@@ -773,6 +828,9 @@ const getEntityType = (url) => {
if (url.indexOf('/app?') > -1) {
entityType = 'app'
}
if (url.indexOf('/subscriber_id?') > -1) {
entityType = 'subscriber_id'
}
return entityType
}
@@ -787,5 +845,8 @@ const getRelateType = (url) => {
if (url.indexOf('/apps?') > -1) {
entityType = 'app'
}
if (url.indexOf('/subscriber_id?') > -1) {
entityType = 'subscriber_id'
}
return entityType
}

View File

@@ -313,15 +313,18 @@ export const api = {
domainBasicInfo: apiVersion + '/entity/explorer/detail/basic/domain', // Domain实体响应结果
ipBasicInfo: apiVersion + '/entity/explorer/detail/basic/ip', // ip实体响应
appBasicInfo: apiVersion + '/entity/explorer/detail/basic/app', // app实体响应
subscriberBasicInfo: apiVersion + '/entity/explorer/detail/basic/subscriber', // subscriber实体响应
domainTags: apiVersion + '/entity/explorer/detail/kb/intelligence/tag/domain', // Domain实体标签响应结果
ipTags: apiVersion + '/entity/explorer/detail/kb/intelligence/tag/ip', // ip实体标签响应结果
appTags: apiVersion + '/entity/explorer/detail/kb/intelligence/tag/app', // app实体标签响应结果
domainThroughput: apiVersion + '/entity/explorer/detail/traffic/throughput/domain', // 实体流量信息
ipThroughput: apiVersion + '/entity/explorer/detail/traffic/throughput/ip', // 实体流量信息
appThroughput: apiVersion + '/entity/explorer/detail/traffic/throughput/app', // 实体流量信息
subscriberThroughput: apiVersion + '/entity/explorer/detail/traffic/throughput/subscriber', // 实体流量信息
domainPerformance: apiVersion + '/entity/explorer/detail/traffic/performance/domain', // domain网络质量
ipPerformance: apiVersion + '/entity/explorer/detail/traffic/performance/ip', // ip网络质量
appPerformance: apiVersion + '/entity/explorer/detail/traffic/performance/app', // app网络质量
subscriberPerformance: apiVersion + '/entity/explorer/detail/traffic/performance/subscriber', // subscriber网络质量
domainRelatedApp: apiVersion + '/entity/explorer/detail/domain/relate/apps', // 域名相关app
domainRelatedIp: apiVersion + '/entity/explorer/detail/domain/relate/ips', // 域名相关ip
appRelatedDomain: apiVersion + '/entity/explorer/detail/app/relate/domains', // app相关域名
@@ -349,7 +352,8 @@ export const api = {
appEventPerformance: apiVersion + '/entity/explorer/detail/event/performance/app', // app服务质量详情
entityActive: apiVersion + '/entity/explorer/overview/active', // entity首页active数据概览
entityNew: apiVersion + '/entity/explorer/overview/new', // entity首页new数据概览
entityTotal: apiVersion + '/entity/explorer/overview/total' // entity首页total数据概览
entityTotal: apiVersion + '/entity/explorer/overview/total', // entity首页total数据概览
subscriberRelatedApp: apiVersion + '/entity/explorer/detail/subscriber/relate/apps' // subscriber相关app
}
},
location: {

View File

@@ -31,7 +31,7 @@ axios.interceptors.request.use(config => {
err => Promise.reject(err)
)
const accountErrorCode = [518003, 518004, 518005, 518006, 518007, 518008] // 账号锁定等
const licenceErrorCode = [711001]
const licenceErrorCode = [715001]
// 若get请求的url中带问号则将url上的参数截取改为对象形式传参
axios.interceptors.request.use(

View File

@@ -95,6 +95,7 @@ export default {
}
},
uploadSuccess (response) {
this.initData()
this.$message.success(this.$t('tip.success'))
},
uploadError (error) {

View File

@@ -64,7 +64,7 @@
<div class="header__title">{{$t('overall.add')}}</div>
<div class="header__operations">
<div class="header__operation header__operation--cancel" @click="cancelApp" test-id="cancel-app">{{$t('overall.cancel')}}</div>
<div class="header__operation header__operation--save" @click="save">{{$t('overall.save')}}</div>
<div class="header__operation header__operation--save" :class="{'no-records':toSaveApp.length === 0}" @click="save">{{$t('overall.save')}}</div>
</div>
</div>
<div class="add-app__body">
@@ -466,7 +466,7 @@ export default {
},
addApp (pageNo, val, show, index) {
if (!index) {
index = '0'
index = this.appTypeTab
}
this.showAddApp = true
const params = {

View File

@@ -46,7 +46,8 @@
<span>{{ summaryCount.totalCount }}&nbsp;</span>{{$t('overall.results')}}IP<span class="margin-r-3"></span>
<span>{{ summaryCount.ipCount }}</span>{{$t('overall.domain')}}<span class="margin-r-3"></span>
<span>{{ summaryCount.domainCount }}</span>APP<span class="margin-r-3"></span>
<span>{{ summaryCount.appCount }}</span>
<span>{{ summaryCount.appCount }}</span>{{$t('overall.subscriber')}}<span class="margin-r-3"></span>
<span>{{ summaryCount.subscriberCount }}</span>
<span class="entity-hide-entity" v-if="q">
<!-- <span v-if="listData.length !== 0">-->
@@ -172,6 +173,36 @@
</div>
</div>
<el-divider direction="vertical"></el-divider>
<div class="entity-overview">
<div class="overview-left">
<span class="overview-left-loading">
<span class="overview-left-loading-span">{{ $t('overall.subscriber') }}</span>
</span>
</div>
<div class="overview-right">
<div class="right-row margin-b-6">
<i class="cn-icon cn-icon-proportion entity-explorer-total__icon"></i>
<div class="right-label">{{ $t('network.total') }}</div>
<div class="right-label-loading">
<loading :loading="loadingIp" size="small"></loading>
<div class="right-value">{{ numberWithCommas(entitySubscriberTotal) }}</div>
</div>
</div>
<div class="right-row">
<i class="cn-icon cn-icon-active"></i>
<div class="right-label">{{ $t('entity.active') }}</div>
<div class="right-label-loading">
<loading :loading="loadingSubscriberActive" size="small"></loading>
<div class="right-value-block">
<span class="margin-r-6">{{ numberWithCommas(entitySubscriberActive) }}</span>
<span class="last-hour">{{ $t('entity.inLastHour') }}</span>
</div>
</div>
</div>
</div>
</div>
<el-divider direction="vertical"></el-divider>
</div>
</div>
</div>
@@ -226,6 +257,10 @@ export default {
entityIpNew: '-',
entityIpActive: '-',
entitySubscriberTotal: '-',
entitySubscriberNew: '-',
entitySubscriberActive: '-',
newFilterData: [
{
icon: 'cn-icon cn-icon-registration-country',
@@ -310,10 +345,12 @@ export default {
loadingApp: false,
loadingDomain: false,
loadingIp: false,
loadingSubscriber: false,
// Active
loadingAppActive: false,
loadingDomainActive: false,
loadingIpActive: false,
loadingSubscriberActive: false,
initFlag: true, // 初始化标志避免初始化时pageSize和pageNo会调用搜索
timer: null, // 初始化标志的延时器,需要销毁
@@ -321,7 +358,8 @@ export default {
totalCount: 0,
domainCount: 0,
ipCount: 0,
appCount: 0
appCount: 0,
subscriberCount: 0
},
loadingCount: false, // 实体基数统计的loading
keywordList: []
@@ -539,6 +577,21 @@ export default {
},
/** 新版查询filter数据 */
queryFilterNew (params) {
const subscriberList = ['subscriber.phone_number', 'subscriber.imei', 'subscriber.imsi', 'subscriber.apn']
let subscriberFlag = false
subscriberList.forEach(item => {
if (params.q.indexOf(item) > -1) {
subscriberFlag = true
}
})
if (subscriberFlag) {
this.newFilterData.forEach(item => {
item.loading = false
item.firstLoad = false
item.data = []
})
return true
}
const queryParams = {
startTime: getSecond(params.startTime),
endTime: getSecond(params.endTime),
@@ -594,6 +647,7 @@ export default {
}).finally(() => {
this.newFilterData[index].loading = false
this.newFilterData[index].firstLoad = false
console.log('查看loading', this.newFilterData)
})
}
})
@@ -614,13 +668,15 @@ export default {
this.listData = []
this.$nextTick(() => {
this.listData = response.data.data.list
/*if (this.listData.length === 0) {
/* if (this.listData.length === 0) {
this.isHideRelatedEntities = false
}*/
} */
})
} else {
this.$message.error(response.data.message)
}
}).catch((e) => {
this.listData = []
}).finally(() => {
this.listLoading = false
})
@@ -639,11 +695,11 @@ export default {
this.summaryCount = response.data.data
this.pageObj.total = response.data.data.totalCount
} else {
this.summaryCount = { totalCount: 0, domainCount: 0, ipCount: 0, appCount: 0 }
this.summaryCount = { totalCount: 0, domainCount: 0, ipCount: 0, appCount: 0, subscriberCount: 0 }
}
}).catch(e => {
console.error(e)
this.summaryCount = { totalCount: 0, domainCount: 0, ipCount: 0, appCount: 0 }
this.summaryCount = { totalCount: 0, domainCount: 0, ipCount: 0, appCount: 0, subscriberCount: 0 }
}).finally(() => {
this.loadingCount = false
})
@@ -672,20 +728,27 @@ export default {
this.loadingApp = true
this.loadingDomain = true
this.loadingIp = true
this.loadingSubscriber = true
// Active
this.loadingAppActive = true
this.loadingDomainActive = true
this.loadingIpActive = true
this.loadingSubscriberActive = true
axios.get(api.entity.entityList.entityTotal).then(response => {
if (response.status === 200) {
this.entityDomainTotal = response.data.data.domainCount
this.entityIpTotal = response.data.data.ipCount
this.entityAppTotal = response.data.data.appCount
this.entitySubscriberTotal = response.data.data.subscriberCount
}
}).catch((e) => {
this.$message.error(e.response.data.message)
}).finally(() => {
this.loadingDomain = false
this.loadingIp = false
this.loadingApp = false
this.loadingSubscriber = false
})
// Active
axios.get(api.entity.entityList.entityActive).then(response => {
@@ -693,10 +756,15 @@ export default {
this.entityDomainActive = response.data.data.domainCount
this.entityIpActive = response.data.data.ipCount
this.entityAppActive = response.data.data.appCount
this.entitySubscriberActive = response.data.data.subscriberCount
}
}).catch((e) => {
this.$message.error(e.response.data.message)
}).finally(() => {
this.loadingDomainActive = false
this.loadingIpActive = false
this.loadingAppActive = false
this.loadingSubscriberActive = false
})
},
setListMode (mode) {

View File

@@ -11,8 +11,11 @@
<div class="cn-entity__row">
<!--标签-->
<div class="cn-entity__header" style="display: flex;">
<span class="cn-entity__header-title" v-high-light="keywordList">{{ entityData.entityValue || 'Unknown' }}</span>
<span v-show="entityData.isRelated">
<template v-if="entityData.entityType">
<span v-if="entityData.entityType==='subscriber_id'" v-high-light="keywordList">{{ entityData.phone_number }}</span>
<span v-else v-high-light="keywordList">{{ entityData.entityValue || 'Unknown' }}</span>
</template>
<span v-show="entityData.isRelated" class="cn-entity__header-icon">
<el-popover
popper-class="my-popper-class"
placement="right"
@@ -89,6 +92,18 @@
<span class="row-item-value">{{ entityData.category ? appRisk(entityData.category.appRisk) : '-' }}</span>
</div>
</template>
<template v-else-if="entityData.entityType === 'subscriber_id'">
<div class="basic-info__item">
<i class="cn-icon cn-icon-account-info"></i>
<span class="row-item-label">{{ $t('entity.subscriberId') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value" v-high-light="keywordList">{{ $_.get(entityData, 'entityValue', '-') || '-' }}</span>
</div>
<div class="basic-info__item">
<i class="cn-icon cn-icon-location"></i>
<span class="row-item-label">{{ $t('overall.location') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">{{$_.get(entityData, 'subscriber_longitude', '-') || '-'}},&nbsp;{{$_.get(entityData, 'subscriber_latitude', '-') || '-'}}</span>
</div>
</template>
<!-- 通用字段 -->
<div class="basic-info__item">
<div class="item__box">
@@ -102,21 +117,7 @@
<!-- 曲线-->
<div class="item-box-loading">
<loading :loading="loading" size="small"></loading>
<div
class="row__charts"
:id="`entityDetailSend${entityType}${listMode}`"
v-if="entityData.entityType === 'domain'">
</div>
<div
class="row__charts"
:id="`entityDetailSend${entityType}${listMode}`"
v-if="entityData.entityType === 'app'">
</div>
<div
class="row__charts"
:id="`entityDetailSend${entityType}${listMode}`"
v-if="entityData.entityType === 'ip'">
</div>
<div class="row__charts" :id="`entityDetailSend${entityType}${listMode}`"></div>
</div>
</div>
</div>
@@ -130,27 +131,13 @@
</span>
<div class="item-box-loading">
<loading :loading="loading" size="small"></loading>
<div
class="row__charts"
:id="`entityDetailReceived${entityType}${listMode}`"
v-if="entityData.entityType === 'domain'">
</div>
<div
class="row__charts"
:id="`entityDetailReceived${entityType}${listMode}`"
v-if="entityData.entityType === 'app'">
</div>
<div
class="row__charts"
:id="`entityDetailReceived${entityType}${listMode}`"
v-if="entityData.entityType === 'ip'">
</div>
<div class="row__charts" :id="`entityDetailReceived${entityType}${listMode}`"></div>
</div>
</div>
</div>
<!--score分数-->
<div class="basic-info__item" style="display: flex;align-items: center;">
<div class="basic-info__item" style="display: flex;align-items: center;" v-if="entityData.entityType !== 'subscriber_id'">
<i class="cn-icon cn-icon-Score"></i>
<div class="row-item-label">
<span class="row-item-label">{{ $t('network.score') }}&nbsp;:&nbsp;&nbsp;</span>
@@ -181,9 +168,11 @@
</div>
</div>
</div>
<div class="new-show-detail">
<div @click="showDetail"><i class="cn-icon cn-icon-detail"></i>{{ $t('overall.detail') }} ></div>
<div @click="showGraph"><i class="cn-icon cn-icon-graph"></i>{{ $t('entities.graph') }} ></div>
<div class="show-detail__block">
<div class="new-show-detail">
<div @click="showDetail"><i class="cn-icon cn-icon-detail"></i>{{ $t('overall.detail') }} ></div>
<div @click="showGraph" v-if="entity.entityType !== 'subscriber_id'"><i class="cn-icon cn-icon-graph"></i>{{ $t('entities.graph') }} ></div>
</div>
</div>
<el-collapse-transition>
<div class="cn-entity__detail-overview" v-if="!isCollapse">
@@ -277,6 +266,10 @@ export default {
url = api.entity.entityList.appBasicInfo
break
}
case ('subscriber_id'): {
url = api.entity.entityList.subscriberBasicInfo
break
}
}
axios.get(`${url}?resource=${this.entity.entityValue}`).then(response => {
this.$nextTick(() => {

View File

@@ -45,7 +45,7 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.throughput')}}</div>
<div class="row__contents">
<div class="row__content">
<div class="row__content row__content-sent">
<div class="row__charts-msg">{{$t('overall.sent')}}
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') : '-'}}
</div>
@@ -321,7 +321,7 @@ export default {
getMillisecond,
dateFormatByAppearance,
getQueryParams (dateRangeValue) {
if (dateRangeValue && (!this.timeFilter.startTime || !this.timeFilter.endTime)) {
if (dateRangeValue) {
// range取 config.js 中配置的值
const { startTime, endTime } = getNowTime(dateRangeValue)
return {

View File

@@ -9,6 +9,9 @@
<template v-else-if="entity.entityType === 'app'">
<app-overview :entity="entity" :time-filter="timeFilter" :keywordList="keywordList" @reloadEntity="getEntity" @eventNum="getEventNum"></app-overview>
</template>
<template v-else-if="entity.entityType === 'subscriber_id'">
<subscriber-overview :entity="entity" :time-filter="timeFilter" :keywordList="keywordList" @reloadEntity="getEntity" @eventNum="getEventNum"></subscriber-overview>
</template>
</div>
</template>
@@ -16,6 +19,7 @@
import App from './App'
import Domain from './Domain'
import Ip from './Ip'
import Subscriber from './Subscriber'
export default {
/* 详情概览 */
@@ -28,7 +32,8 @@ export default {
components: {
'domain-overview': Domain,
'app-overview': App,
'ip-overview': Ip
'ip-overview': Ip,
'subscriber-overview': Subscriber
},
methods: {
getEntity (data) {

View File

@@ -51,7 +51,7 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.throughput')}}</div>
<div class="row__contents">
<div class="row__content">
<div class="row__content row__content-sent">
<div class="row__charts-msg">{{$t('overall.sent')}}
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') : '-'}}
</div>
@@ -325,7 +325,7 @@ export default {
getMillisecond,
dateFormatByAppearance,
getQueryParams (dateRangeValue) {
if (dateRangeValue && (!this.timeFilter.startTime || !this.timeFilter.endTime)) {
if (dateRangeValue) {
// range取 config.js 中配置的值
const { startTime, endTime } = getNowTime(dateRangeValue)
return {

View File

@@ -87,7 +87,7 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.throughput')}}</div>
<div class="row__contents">
<div class="row__content">
<div class="row__content row__content-sent">
<div class="row__charts-msg">{{$t('overall.sent')}}:
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') : '-'}}
</div>
@@ -409,7 +409,7 @@ export default {
getMillisecond,
dateFormatByAppearance,
getQueryParams (dateRangeValue) {
if (dateRangeValue && (!this.timeFilter.startTime || !this.timeFilter.endTime)) {
if (dateRangeValue) {
// range取 config.js 中配置的值
const { startTime, endTime } = getNowTime(dateRangeValue)
return {

View File

@@ -0,0 +1,261 @@
<template>
<div class="overview-item">
<div class="overview__title">{{$t('overall.basicInfo')}}</div>
<div class="overview__content">
<div class="overview__row">
<div class="row__label row__label--width130">{{ $t('entity.subscriberId') }}</div>
<div class="row__content">{{$_.get(entity, 'entityValue', '-') || '-'}}</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.location')}}</div>
<div class="row__content" v-high-light="keywordList">{{$_.get(entity, 'subscriber_longitude', '-') || '-'}},&nbsp;{{$_.get(entity, 'subscriber_latitude', '-') || '-'}}</div>
</div>
</div>
</div>
<div class="overview-item">
<div class="overview__title">{{$t('overall.traffic')}}</div>
<div class="overview__content overview__content-loading">
<loading :loading="loadingTraffic" size="small" inner-style="left: 8.75rem;" style="width: 50%;"></loading>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.peak')}}</div>
<div class="row__content">
{{valueToRangeValue(entityData.max, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.max, unitTypes.bps).join(' ') : '-'}}
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.average')}}</div>
<div class="row__content">
{{valueToRangeValue(entityData.avg, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.avg, unitTypes.bps).join(' ') : '-'}}
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.throughput')}}</div>
<div class="row__contents row__contents-subscriber">
<div class="row__content row__content-sent">
<div class="row__charts-msg">{{$t('overall.sent')}}
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.bps).join(' ') : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
<div class="row__charts" :id="`entityDetailSend${entity.entityValue}`" ></div>
</div>
</div>
<div class="row__content row__content-accept">
<div class="row__charts-msg">{{$t('overall.received')}}
{{valueToRangeValue(entityData.bytesReceivedRate, unitTypes.bps).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesReceivedRate, unitTypes.bps).join(' ') : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
<div class="row__charts" :id="`entityDetailReceived${entity.entityValue}`" ></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overview-item">
<div class="overview__title">{{$t('overall.relationship')}}</div>
<div class="overview__content domain__content">
<div class="overview__tags domain__tags" ref="relationship"></div>
<div class="overview__row overview__row-related">
<div class="row__label row__label--width130">{{$t('entities.tab.relatedApp')}}</div>
<div class="row__content">
<div v-if="loadingRelationshipOne" style="position: relative;width: 450px;">
<loading :loading="loadingRelationshipOne" size="small" style="left: 1rem;"></loading>
</div>
<div class="data-item high-light-block" v-high-light="keywordList" v-show="item.show" v-for="(item, index) in relationshipDataOne" :key="index">
{{item.value}}
</div>
<div v-if="relationshipDataOne.length===0 && !loadingRelationshipOne">-</div>
<div v-if="relationshipShowOne">
<div class="data-item show-more-related" id="related-app-more" @click.stop="showMoreApp" style="position: relative">...</div>
<div v-if="isShowMoreApp" class="app-popover_block" id="showRelatedApp">
<div class="popover-content" v-for="(item, index) in relationshipDataOne" :key="index">
<span v-if="!item.show" class="high-light-block" v-high-light="keywordList">{{item.value}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="overview-map overview-map--app">
<!-- <chart-->
<!-- :chart-info="chart"-->
<!-- :chart-data="chartData"-->
<!-- :entity="entityCopy"-->
<!-- :query-params="queryParams"-->
<!-- :hide-header="true"-->
<!-- :loading="loadingMap"-->
<!-- @getCurrentTimeRange="getCurrentTimeRange"-->
<!-- ></chart>-->
<subscriber-map :entity="entity"></subscriber-map>
</div>
</template>
<script>
import { api } from '@/utils/api'
import entityDetailMixin from './entityDetailMixin'
import { unitTypes } from '@/utils/constants'
import { valueToRangeValue } from '@/utils/unit-convert'
import _ from 'lodash'
import axios from 'axios'
import relatedServer from '@/mixins/relatedServer'
import { dateFormatByAppearance, getMillisecond, getSecond, getNowTime } from '@/utils/date-util'
import Loading from '@/components/common/Loading'
import { ref } from 'vue'
import SubscriberMap from '@/views/entityExplorer/entityList/detailOverview/SubscriberMap'
export default {
name: 'Subscriber',
mixins: [entityDetailMixin, relatedServer],
components: {
Loading,
SubscriberMap
},
props: {
keywordList: Array
},
data () {
return {
// entityData: {}
entityType: 'subscriber',
// trafficUrl: api.entityAppDetailTraffic,
trafficUrl: api.entity.entityList.subscriberThroughput,
// relationUrl: api.entityAppDetailRelation,
// networkQuantityUrl: api.entityAppDetailNetworkQuantity,
networkQuantityUrl: api.entity.entityList.subscriberPerformance,
// linkInUrl: api.entityAppDetailLinkIn,
// linkOutUrl: api.entityAppDetailLinkOut,
// performanceUrl: api.entityAppDetailPerformance,
// securityUrl: api.entityAppDetailSecurity,
// trafficUrlMap: api.entityAppDetailTrafficMap,
// trafficUrlMap: api.entity.entityList.appTrafficMap,
// relatedServerDomainUrl: api.entity.entityList.appRelatedDomain,
relatedServerAppUrl: api.entity.entityList.subscriberRelatedApp,
chartData: null,
listMode: 'list',
singleValues: {
chartInfos: [
{
params: {
icon: 'cn-icon cn-icon-time',
unitType: unitTypes.time,
iconColor: '#2ca3fe',
iconBackgroundColor: '#eff6fe'
},
type: 507,
i18n: 'entities.avgRoundTripTime'
},
{
params: {
icon: 'cn-icon cn-icon-http',
unitType: unitTypes.time,
iconColor: '#2ca3fe',
iconBackgroundColor: '#eff6fe'
},
type: 507,
i18n: 'entities.httpResponseLatency'
},
{
params: {
icon: 'cn-icon cn-icon-ssl',
unitType: unitTypes.time,
iconColor: '#2ca3fe',
iconBackgroundColor: '#eff6fe'
},
type: 507,
i18n: 'entities.sslConLatency'
},
{
params: {
icon: 'cn-icon cn-icon-package-loss',
unitType: unitTypes.percent,
iconColor: '#2ca3fe',
iconBackgroundColor: '#eff6fe'
},
type: 507,
i18n: 'entities.sequenceGapLossPercent'
},
{
params: {
icon: 'cn-icon cn-icon-upload',
unitType: unitTypes.percent,
iconColor: '#2ca3fe',
iconBackgroundColor: '#eff6fe'
},
type: 507,
i18n: 'entities.pktRetransPercent'
}
],
chartDatas: [null, null, null, null, null]
},
loadingTraffic: false,
loadingRelationshipOne: false,
loadingRelationshipTwo: false,
loadingNetworkQuality: false,
loadingOut: false,
loadingIn: false,
loadingAlert: false,
loadingSecurityEvents: false,
loadingMap: false
}
},
methods: {
getMillisecond,
dateFormatByAppearance,
getQueryParams (dateRangeValue) {
if (dateRangeValue) {
// range取 config.js 中配置的值
const { startTime, endTime } = getNowTime(dateRangeValue)
return {
startTime: getSecond(startTime),
endTime: getSecond(endTime),
resource: this.entity.entityValue
}
} else {
return {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
resource: this.entity.entityValue
}
}
},
getPerformanceQueryParams () {
return {
appName: this.entity.entityValue
}
},
handleRelationData (result) {
this.entityData.domainCount = result.domainCount
this.entityData.ipCount = result.ipCount
},
queryRelated () {
this.getRelatedServerDataOne(this.relatedServerAppUrl)
}
},
mounted () {
this.queryParams = this.getQueryParams()
this.$nextTick(() => {
setTimeout(() => {
this.queryRelated()
}, 250)
})
},
setup (props) {
const entityData = ref({ ...props.entity })
return {
unitTypes,
entityCopy: {
..._.cloneDeep(props.entity)
},
valueToRangeValue,
entityData
}
}
}
</script>

View File

@@ -0,0 +1,713 @@
<template>
<div class="subscriber-map">
<div class="subscriber-map-body" style="height: 100%">
<loading :loading="trackingMapLoading" @click="developmentClick"></loading>
<div id="subscriberMap" class="entity-subscriber-map"></div>
</div>
<div class="geo-analysis__hexagon-tooltip" id="tooltip" :class="`geo-analysis__hexagon-tooltip--${tooltip.type}`" v-if="tooltip.showMarkerTooltip || tooltip.showPolygonTooltip" :style="{'left': `${tooltip.x}px`, 'top': `${tooltip.y}px`}" @mouseenter="tooltipMouseEnter" @mouseleave="tooltipMouseLeave">
<div class="hexagon-tooltip__header" :style="`background-color: ${tooltipHeaderColor}`">
<div class="header__icon">
<svg v-if="tooltip.type === tooltipType.hexagon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="36" height="36"><path d="M747.52 921.088H277.504L42.496 514.048l235.008-407.04H747.52l235.008 407.04-235.008 407.04z m-425.472-76.8h381.44l190.464-330.24-190.464-330.24h-381.44l-190.464 330.24 190.464 330.24z"></path></svg>
<template v-else-if="tooltip.type === tooltipType.human">
<div class="icon__box">
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M366.689524 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L366.689524 690.468571zM203.824762 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571429 8.435809l-99.181714-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634286 193.487238 88.405334 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182858 27.160381c-5.851429 2.048-12.092952 3.120762-18.383238 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L365.616762 623.177143a84.894476 84.894476 0 0 1-24.868572-80.700953l34.523429-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L290.133333 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C420.08381 58.465524 473.86819 17.066667 533.650286 23.30819 593.383619 29.549714 636.537905 81.13981 630.00381 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z"></path></svg>
</div>
</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">
<div class="icon__box">
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M164.901926 519.585185a391.35763 391.35763 0 0 1-30.151111-152.38637c0-52.527407 10.05037-105.054815 30.151111-152.348445 15.094519-47.29363 45.283556-89.353481 80.516741-126.103703L174.990222 15.17037C129.706667 62.464 94.511407 114.991407 69.328593 178.062222 44.183704 241.095111 34.133333 304.165926 34.133333 367.198815c0 63.070815 10.05037 131.375407 35.19526 189.174518 25.182815 57.799111 60.378074 115.598222 105.661629 162.891852l70.428445-73.576296c-35.233185-36.788148-65.422222-78.810074-80.516741-126.103704z" p-id="8786"></path><path d="M255.469037 477.563259c15.094519 36.788148 35.195259 68.266667 60.340148 94.549334l70.428445-73.576297a233.168593 233.168593 0 0 1-40.201482-57.761185c-5.044148-26.282667-10.088296-47.29363-10.088296-73.576296 0-26.244741 5.044148-47.29363 15.094518-68.266667 5.006222-26.282667 20.100741-42.097778 35.19526-63.070815l-70.428445-73.576296c-25.144889 26.282667-45.24563 57.799111-60.340148 94.587259-15.094519 36.788148-20.100741 73.576296-20.100741 110.364445 0 36.788148 5.006222 73.53837 20.100741 110.326518zM436.527407 367.198815c0 43.538963 33.792 78.810074 75.472593 78.810074s75.472593-35.271111 75.472593-78.810074c0-43.501037-33.792-78.810074-75.472593-78.810074s-75.472593 35.271111-75.472593 78.810074zM637.76237 498.574222l70.428445 73.576297c25.144889-26.282667 45.24563-57.837037 60.340148-94.58726 15.094519-36.788148 20.100741-73.576296 20.100741-110.364444 0-36.788148-5.006222-73.576296-20.100741-110.364445-15.094519-36.750222-35.195259-68.266667-60.340148-94.549333l-70.428445 73.576296c15.094519 15.739259 30.189037 36.788148 40.201482 57.799111 10.088296 21.010963 15.132444 47.29363 15.132444 68.266667 0 26.282667-5.044148 47.331556-15.094518 68.342519-10.05037 31.516444-25.144889 47.29363-40.239408 68.266666z" p-id="8787"></path><path d="M954.671407 178.062222C929.488593 114.991407 894.293333 62.464 849.009778 15.17037L778.619259 88.746667c35.233185 36.788148 60.378074 78.810074 80.516741 126.103703 20.100741 47.255704 30.151111 99.821037 30.151111 152.348445 0 52.565333-10.05037 105.092741-30.151111 152.38637-20.100741 47.29363-45.283556 89.315556-80.516741 126.103704l70.428445 73.576296c45.283556-47.29363 80.478815-99.858963 105.661629-162.891852 25.144889-63.070815 35.195259-126.103704 35.19526-189.174518 0-63.032889-10.05037-131.337481-35.19526-189.136593zM210.185481 1024h603.629038L512 551.10163 210.185481 1024z m186.102519-105.054815L512 740.238222l115.674074 178.631111h-231.348148z"></path></svg>
</div>
</template>
</div>
<div class="header__title">
<template v-if="tooltip.type === tooltipType.hexagon">HEX</template>
<template v-else-if="tooltip.type === tooltipType.human">MSISDN</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">CID</template>
</div>
<div class="header__content">
<template v-if="tooltip.type === tooltipType.hexagon">{{currentPolygon.hexId}}</template>
<template v-else-if="tooltip.type === tooltipType.human">{{currentSubscriber.subscriberDto.phoneNumber}}</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">0xxa8805</template>
</div>
</div>
<div class="hexagon-tooltip__body">
<template v-if="tooltip.type === tooltipType.hexagon">
<template v-for="(item, i) in JSON.parse(currentPolygon.locations)" :key="item.hexId">
<div class="body__timeline" v-if="i < 5">
<div class="timeline-symbol"></div>
<div>
<div class="body__item">
<div class="item__label">{{ $t('location.location') }}</div>
<div class="item__value">{{item.longitude}},&nbsp;{{item.latitude}}</div>
</div>
<div class="body__item">
<div class="item__label">Time</div>
<div class="item__value">{{dateFormatByAppearance(Number(item.time))}}</div>
</div>
</div>
</div>
</template>
<div class="body__timeline" v-if="JSON.parse(currentPolygon.locations).length > 5">...</div>
</template>
<template v-else-if="tooltip.type === tooltipType.human">
<div class="body__item">
<div class="item__label">ID</div>
<div class="item__value">{{currentSubscriber.subscriberId}}</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('entities.group')}}</div>
<div class="item__value">Terrorist</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('overall.info')}}</div>
<div class="item__value">Leader</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('overall.location')}}</div>
<div class="item__value">China, Shanghai</div>
</div>
<div class="body__tracking" @click="trackSubscriber(currentSubscriber)">{{$t('location.traceTracking')}}</div>
</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">
<div class="body__item">
<div class="item__label">{{ $t('location.locationAreaCode') }}</div>
<div class="item__value">12</div>
</div>
<div class="body__item">
<div class="item__label">{{ $t('location.mobileNetworkCode') }}</div>
<div class="item__value">1</div>
</div>
<div class="body__item">
<div class="item__label">{{ $t('location.communicationType') }}</div>
<div class="item__value">4G</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('overall.location')}}</div>
<div class="item__value">China, Shanghai</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import chartMixin from '@/views/charts2/chart-mixin'
import maplibregl from 'maplibre-gl'
import mapStyle from '@/views/charts2/charts/entityDetail/mapStyle'
import axios from 'axios'
import { api } from '@/utils/api'
import 'maplibre-gl/dist/maplibre-gl.css'
import { ref, shallowRef } from 'vue'
import { getNowTime, getSecond, dateFormatByAppearance } from '@/utils/date-util'
import unitConvert from '@/utils/unit-convert'
import { defaultMapConfig, storageKey, unitTypes } from '@/utils/constants'
import { h3ToGeo, h3ToGeoBoundary } from 'h3-js'
import { overwriteUrl, urlParamsHandler } from '@/utils/tools'
import Loading from '@/components/common/Loading'
const humanSvg = '<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M366.689524 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L366.689524 690.468571zM203.824762 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571429 8.435809l-99.181714-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634286 193.487238 88.405334 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182858 27.160381c-5.851429 2.048-12.092952 3.120762-18.383238 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L365.616762 623.177143a84.894476 84.894476 0 0 1-24.868572-80.700953l34.523429-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L290.133333 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C420.08381 58.465524 473.86819 17.066667 533.650286 23.30819 593.383619 29.549714 636.537905 81.13981 630.00381 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z"></path></svg>'
const baseStationSvg = '<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M164.901926 519.585185a391.35763 391.35763 0 0 1-30.151111-152.38637c0-52.527407 10.05037-105.054815 30.151111-152.348445 15.094519-47.29363 45.283556-89.353481 80.516741-126.103703L174.990222 15.17037C129.706667 62.464 94.511407 114.991407 69.328593 178.062222 44.183704 241.095111 34.133333 304.165926 34.133333 367.198815c0 63.070815 10.05037 131.375407 35.19526 189.174518 25.182815 57.799111 60.378074 115.598222 105.661629 162.891852l70.428445-73.576296c-35.233185-36.788148-65.422222-78.810074-80.516741-126.103704z" p-id="8786"></path><path d="M255.469037 477.563259c15.094519 36.788148 35.195259 68.266667 60.340148 94.549334l70.428445-73.576297a233.168593 233.168593 0 0 1-40.201482-57.761185c-5.044148-26.282667-10.088296-47.29363-10.088296-73.576296 0-26.244741 5.044148-47.29363 15.094518-68.266667 5.006222-26.282667 20.100741-42.097778 35.19526-63.070815l-70.428445-73.576296c-25.144889 26.282667-45.24563 57.799111-60.340148 94.587259-15.094519 36.788148-20.100741 73.576296-20.100741 110.364445 0 36.788148 5.006222 73.53837 20.100741 110.326518zM436.527407 367.198815c0 43.538963 33.792 78.810074 75.472593 78.810074s75.472593-35.271111 75.472593-78.810074c0-43.501037-33.792-78.810074-75.472593-78.810074s-75.472593 35.271111-75.472593 78.810074zM637.76237 498.574222l70.428445 73.576297c25.144889-26.282667 45.24563-57.837037 60.340148-94.58726 15.094519-36.788148 20.100741-73.576296 20.100741-110.364444 0-36.788148-5.006222-73.576296-20.100741-110.364445-15.094519-36.750222-35.195259-68.266667-60.340148-94.549333l-70.428445 73.576296c15.094519 15.739259 30.189037 36.788148 40.201482 57.799111 10.088296 21.010963 15.132444 47.29363 15.132444 68.266667 0 26.282667-5.044148 47.331556-15.094518 68.342519-10.05037 31.516444-25.144889 47.29363-40.239408 68.266666z" p-id="8787"></path><path d="M954.671407 178.062222C929.488593 114.991407 894.293333 62.464 849.009778 15.17037L778.619259 88.746667c35.233185 36.788148 60.378074 78.810074 80.516741 126.103703 20.100741 47.255704 30.151111 99.821037 30.151111 152.348445 0 52.565333-10.05037 105.092741-30.151111 152.38637-20.100741 47.29363-45.283556 89.315556-80.516741 126.103704l70.428445 73.576296c45.283556-47.29363 80.478815-99.858963 105.661629-162.891852 25.144889-63.070815 35.195259-126.103704 35.19526-189.174518 0-63.032889-10.05037-131.337481-35.19526-189.136593zM210.185481 1024h603.629038L512 551.10163 210.185481 1024z m186.102519-105.054815L512 740.238222l115.674074 178.631111h-231.348148z"></path></svg>'
export default {
name: 'EntityDetailMap',
mixins: [chartMixin],
components: {
Loading
},
data () {
return {
tooltipType: {
hexagon: 'hexagon',
baseStation: 'base-station',
human: 'human'
}
}
},
mounted () {
this.initMap()
},
unmounted () {
this.mapChart && this.mapChart?.remove?.()
this.mapChart = null
},
computed: {
tooltipHeaderColor () {
if (this.tooltip.type === this.tooltipType.hexagon) {
const color = this.currentPolygon.color.split(',')
color[0] = color[0].split('[')[1]
color[2] = color[2].split(']')[0]
return `rgba(${color.join(',')},.8)`
} else if (this.tooltip.type === this.tooltipType.human) {
return '#38ACD2'
} else if (this.tooltip.type === this.tooltipType.baseStation) {
return '#233447'
}
return ''
}
},
watch: {
// 切换追踪的用户
currentShowSubscriber (n) {
this.mapChart.getLayer('trackingHexagon') && this.mapChart.removeLayer('trackingHexagon')
this.mapChart.getLayer('trackingLine') && this.mapChart.removeLayer('trackingLine')
this.mapChart.getSource('trackingHexGrid') && this.mapChart.removeSource('trackingHexGrid')
this.mapChart.getSource('trackingLineSource') && this.mapChart.removeSource('trackingLineSource')
this.trackingHumanMarker.remove && this.trackingHumanMarker.remove()
this.trackingHumanMarker = {}
if (n) {
this.renderTrackingHexagon()
}
},
timeFilter (n) {
this.unbindTrackingHexagonEvents()
this.mapChart.getLayer('trackingHexagon') && this.mapChart.removeLayer('trackingHexagon')
this.mapChart.getLayer('trackingLine') && this.mapChart.removeLayer('trackingLine')
this.mapChart.getSource('trackingHexGrid') && this.mapChart.removeSource('trackingHexGrid')
this.mapChart.getSource('trackingLineSource') && this.mapChart.removeSource('trackingLineSource')
this.trackingHumanMarker.remove && this.trackingHumanMarker.remove()
this.trackingHumanMarker = {}
this.changeTimeFilterToInitList()
this.initTraceTrackingTab()
}
},
methods: {
dateFormatByAppearance,
async initMap () {
const _this = this
this.toggleLoading(false)
this.mapChart = new maplibregl.Map({
container: 'subscriberMap',
style: mapStyle,
center: this.center,
maxZoom: this.maxZoom,
minZoom: this.minZoom,
zoom: this.defaultZoom
})
maplibregl.addProtocol('cn', (params, callback) => { // 切片显示接口 防止跨域的问题
fetch(`${params.url.split('://')[1]}`)
.then(t => {
if (t.status == 200) {
t.arrayBuffer().then(arr => {
callback(null, arr, null, null)
})
} else {
callback(new Error(`Tile fetch error: ${t.statusText}`))
}
})
.catch(e => {
callback(new Error(e))
})
return { cancel: () => { } }
})
this.mapChart.on('load', async () => {
const baseStationData = await _this.queryBaseStation()
_this.renderMarker(baseStationData, _this.tooltipType.baseStation)
_this.initTraceTrackingTab()
})
},
async initTraceTrackingTab () {
await this.queryTraceTracking()
if (!this.currentShowSubscriber && this.trackingSubscriber) {
this.currentShowSubscriber = this.trackingSubscriber
}
this.renderTrackingHexagon()
},
async queryBaseStation () {
// this.loading.baseStationLoading = true
try {
// const response = await axios.get(api.location.baseStation)
const response = [
{
longitude: 116.38,
latitude: 39.9
},
{
longitude: 116.39,
latitude: 39.9
},
{
longitude: 116.383,
latitude: 39.886
},
{
longitude: 116.378,
latitude: 39.902
},
{
longitude: 116.369,
latitude: 39.91
},
{
longitude: 116.38,
latitude: 39.91
}
]
return response // response.data.data.list
} catch (e) {
this.errorMsgHandler(e)
console.error(e)
} finally {
// this.loading.baseStationLoading = false
}
return []
},
reload (startTime, endTime, dateRangeValue) {
this.timeFilter = { startTime: getSecond(startTime), endTime: getSecond(endTime), dateRangeValue: dateRangeValue }
const { query } = this.$route
this.$store.commit('setTimeRangeArray', [this.timeFilter.startTime, this.timeFilter.endTime])
this.$store.commit('setTimeRangeFlag', dateRangeValue.value)
const newUrl = urlParamsHandler(window.location.href, query, {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
range: dateRangeValue.value
})
overwriteUrl(newUrl)
},
async queryTraceTracking () {
const params = {
startTime: getSecond(this.timeFilter.startTime),
endTime: getSecond(this.timeFilter.endTime),
dateRangeValue: this.timeFilter.dateRangeValue,
subscriberIds: `'${this.entity.entityValue}'`,
level: this.mapLevel
}
this.trackingSubscriber.subscriberId = this.entity.entityValue
this.trackingMapLoading = true
try {
const response = await axios.get(api.location.tracking, { params })
this.trackingMapLoading = false
if (response.data.data.result) {
this.trackingNoData = false
this.showTrackingError = false
const find = response.data.data.result.find(item => item.subscriberId === this.trackingSubscriber.subscriberId)
if (find) {
this.trackingSubscriber.trackRecords = find.trackRecords
} else {
this.trackingSubscriber.trackRecords = []
}
this.trackingSubscriber.show = false
} else {
this.trackingSubscriber.trackRecords = []
}
// 计算停留时间
if (this.trackingSubscriber.trackRecords.length > 0) {
const trackRecords = this.trackingSubscriber.trackRecords
// 初始化时间线可视范围角标
if (trackRecords.length < 11) {
this.trackingSubscriber.scrollStartIndex = 0
this.trackingSubscriber.scrollEndIndex = trackRecords.length
} else {
this.trackingSubscriber.scrollStartIndex = 0
this.trackingSubscriber.scrollEndIndex = 11
}
if (trackRecords && trackRecords.length > 0) {
for (let i = 0; i < trackRecords.length; i++) {
if (i > 0) {
if ((trackRecords[i - 1].subscriberLongitude === trackRecords[i].subscriberLongitude) && (trackRecords[i - 1].subscriberLatitude === trackRecords[i].subscriberLatitude)) {
// 如果连续两条地址重复,则将时间累加,并将上一条删除,键值-1继续循环
if (i > 1 && trackRecords[i - 2]) {
const stayTime = unitConvert(trackRecords[i - 2].time - trackRecords[i].time, unitTypes.time, 's')
if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) {
stayTime[0] = Number(stayTime[0]).toFixed(0)
}
trackRecords[i].stayTime = stayTime.join(' ')
} else {
// 数据只有2条或者第1条和第2条地点重复删除1合并时间
const stayTime = unitConvert(trackRecords[i - 1].time - trackRecords[i].time, unitTypes.time, 's')
if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) {
stayTime[0] = Number(stayTime[0]).toFixed(0)
}
trackRecords[i].stayTime = stayTime.join(' ')
}
trackRecords.splice(i - 1, 1)
i = i - 1
} else {
const stayTime = unitConvert(trackRecords[i - 1].time - trackRecords[i].time, unitTypes.time, 's')
if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) {
stayTime[0] = Number(stayTime[0]).toFixed(0)
}
trackRecords[i].stayTime = stayTime.join(' ')
}
if (i === trackRecords.length - 1) {
// 初始化数据时,重置偏移量和列表高度
this.trackingSubscriber.startOffset = 0
this.trackingSubscriber.listHeight = i * this.scrollInfo.itemSize
}
} else {
trackRecords[i].stayTime = '-'
}
}
}
this.trackingSubscriber.trackRecords = trackRecords
} else {
this.trackingNoData = true
}
} catch (e) {
this.showTrackingError = true
this.trackingNoData = false
this.trackingErrorMsg = this.errorMsgHandler(e)
console.error(e)
} finally {
this.trackingMapLoading = false
}
},
onScroll (e) {
// 当前滚动位置
const scrollTop = e.target.scrollTop
// 列表开始索引
const startIndex = Math.floor(scrollTop / this.scrollInfo.itemSize) || 0
// 列表结束索引
const endIndex = Math.ceil((scrollTop + this.scrollInfo.containerHeight) / this.scrollInfo.itemSize)
this.trackingSubscriber.scrollStartIndex = startIndex
this.trackingSubscriber.scrollEndIndex = endIndex
// 列表距离顶部距离
this.trackingSubscriber.startOffset = scrollTop - (scrollTop % this.scrollInfo.itemSize)
},
tooltipMouseEnter () {
this.tooltip.mouseInMarkerOrTooltip = true
},
tooltipMouseLeave (event) {
if (this.currentMarkerDom && !this.currentMarkerDom.contains(event.relatedTarget)) {
this.tooltip.mouseInMarkerOrTooltip = false
this.tooltip.showMarkerTooltip = false
this.currentMarkerDom.classList.remove('map-marker--hover')
}
},
trackingHexagonMouseEnter () {
this.tooltip.mouseIsInPolygon = true
},
trackingHexagonMouseLeave () {
this.tooltip.showPolygonTooltip = false
this.tooltip.mouseIsInPolygon = false
// 去掉上一块的高亮
this.hoverTrigger('trackingHexGrid', this.currentPolygon.id, false)
},
trackingHexagonMouseMove (e) {
const { originalEvent, features } = e
if (!this.tooltip.mouseInMarkerOrTooltip) {
this.tooltip.showPolygonTooltip = true
this.tooltip.type = this.tooltipType.hexagon
if (this.tooltip.type === this.tooltipType.hexagon && this.currentPolygon.id && this.currentPolygon.id !== features[0].id) {
// 去掉上一块的高亮
this.hoverTrigger('trackingHexGrid', this.currentPolygon.id, false)
}
this.currentPolygon = features[0].properties
this.currentPolygon.id = features[0].id
this.currentPolygon.location = `${h3ToGeo(this.currentPolygon.hexId)[1]}, ${h3ToGeo(this.currentPolygon.hexId)[0]}`
this.tooltip.x = originalEvent.clientX + 15
this.tooltip.y = originalEvent.clientY + 5
// 鼠标滑过高亮
this.hoverTrigger('trackingHexGrid', this.currentPolygon.id, true)
}
},
bindMarkerEvent (el, markerData, type) {
el.addEventListener('mouseenter', e => {
this.currentMarkerDom = el
if (type === this.tooltipType.human) {
this.currentSubscriber = markerData
if (!this.tooltip.mouseInMarkerOrTooltip) {
this.tooltip.x = e.clientX + 15 - e.offsetX
this.tooltip.y = (e.clientY + 15 - e.offsetY + this.tooltipDomHeight.human) > this.mapDomHeight ? (this.mapDomHeight - this.tooltipDomHeight.human) : (e.clientY + 15 - e.offsetY)
}
} else if (type === this.tooltipType.baseStation) {
this.currentBaseStation = markerData
if (!this.tooltip.mouseInMarkerOrTooltip) {
this.tooltip.x = e.clientX + 15 - e.offsetX
this.tooltip.y = (e.clientY + 15 - e.offsetY + this.tooltipDomHeight.baseStation) > this.mapDomHeight ? (this.mapDomHeight - this.tooltipDomHeight.baseStation) : (e.clientY + 15 - e.offsetY)
}
}
this.tooltip.mouseInMarkerOrTooltip = true
this.tooltip.type = type
this.tooltip.showMarkerTooltip = true
el.classList.add('map-marker--hover')
})
el.addEventListener('mouseleave', event => {
const tooltipDom = document.getElementById('tooltip')
if (!tooltipDom.contains(event.relatedTarget)) {
el.classList.remove('map-marker--hover')
this.tooltip.mouseInMarkerOrTooltip = false
this.tooltip.showMarkerTooltip = false
}
})
if (type === this.tooltipType.human) {
el.addEventListener('click', e => {
this.humanMarkers.forEach(m => {
m.getElement().classList.remove('map-marker--highlight')
})
if (this.highlightSubscriber.subscriberId !== markerData.subscriberId) {
el.classList.add('map-marker--highlight')
this.highlightSubscriber = markerData
// 将滚动条跳转到对应位置
document.querySelector(`#locationMap-subscriberId-${markerData.subscriberId}`).scrollIntoView({ behavior: 'smooth', block: 'center' })
} else {
this.highlightSubscriber = {}
}
})
}
},
// 地图上人图标鼠标悬浮框中点击追踪事件
trackSubscriber (subscriber) {
const find = this.trackingSubscribers.find(s => s.subscriberId === subscriber.subscriberId)
if (!find) {
this.trackingSubscribers.push({ ...subscriber, show: false, showLine: false, scrollStartIndex: 0, scrollEndIndex: 11, startOffset: 0, listHeight: 0 })
}
this.currentShowSubscriber = subscriber
this.tooltip.showMarkerTooltip = false
},
unbindTrackingHexagonEvents () {
this.mapChart.off('mouseenter', this.trackingHexagonMouseEnter)
this.mapChart.off('mouseleave', this.trackingHexagonMouseLeave)
this.mapChart.off('mousemove', this.trackingHexagonMouseMove)
},
renderTrackingMarker (coordinates) {
const el = document.createElement('div')
el.className = 'map-tracking-marker'
el.innerHTML = `<div class="tracking-marker__inner-circle">${humanSvg}</div>`
this.trackingHumanMarker = new maplibregl.Marker({ element: el }).setLngLat(coordinates).addTo(this.mapChart)
},
renderTrackingHexagon () {
if (!this.currentShowSubscriber) {
return true
}
const currentShowSubscriberRecords = this.currentShowSubscriber.trackRecords
if (currentShowSubscriberRecords && currentShowSubscriberRecords.length > 0) {
// 六边形
this.trackingPolygonSourceData = this.hexagonDataConverter(this.currentShowSubscriber.trackRecords)
this.mapChart.addSource('trackingHexGrid', {
type: 'geojson',
data: this.trackingPolygonSourceData
})
this.mapChart.addLayer({
id: 'trackingHexagon',
type: 'fill',
source: 'trackingHexGrid',
layout: {},
paint: {
'fill-color': ['get', 'color'],
'fill-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 0.75, 0.5]
}
})
// 轨迹线
const mapLineSourceData = this.mapLineDataConverter()
this.mapChart.addSource('trackingLineSource', {
type: 'geojson',
data: mapLineSourceData
})
this.mapChart.addLayer({
id: 'trackingLine',
type: 'line',
source: 'trackingLineSource',
paint: {
'line-color': 'rgba(222, 52, 52, .8)',
'line-width': 3
}
})
// 最后所在地的图标
const coordinate = h3ToGeo(currentShowSubscriberRecords[0].hexId)
this.renderTrackingMarker([coordinate[1], coordinate[0]])
this.bindTrackingHexagonEvents()
}
},
mapLineDataConverter () {
const records = this.currentShowSubscriber.trackRecords
const feature = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: records.map(d => {
const cs = h3ToGeo(d.hexId)
return [cs[1], cs[0]]
})
}
}
return {
type: 'FeatureCollection',
features: [feature]
}
},
renderMarker (data, type) {
let svg
if (type === this.tooltipType.baseStation) {
svg = baseStationSvg
} else if (type === this.tooltipType.human) {
svg = humanSvg
}
try {
data.forEach(marker => {
if (type === this.tooltipType.human && marker.subscriberDto) {
const el = document.createElement('div')
el.className = `map-marker map-marker--${type}`
if (marker.subscriberId === this.highlightSubscriber.subscriberId) {
el.classList.add('map-marker--highlight')
}
el.innerHTML = svg
// 鼠标事件控制tooltip显示和marker尺寸
this.bindMarkerEvent(el, marker, type)
const mapMarker = new maplibregl.Marker({ element: el }).setLngLat([marker.subscriberDto.subscriberLongitude, marker.subscriberDto.subscriberLatitude]).addTo(this.mapChart)
mapMarker.subscriberId = marker.subscriberId
this.humanMarkers.push(mapMarker)
} else if (type === this.tooltipType.baseStation) {
const el = document.createElement('div')
el.className = `map-marker map-marker--${type}`
el.innerHTML = svg
// 鼠标事件控制tooltip显示和marker尺寸
this.bindMarkerEvent(el, marker, type)
const mapMarker = new maplibregl.Marker({ element: el }).setLngLat([marker.longitude, marker.latitude]).addTo(this.mapChart)
this.baseStationMarkers.push(mapMarker)
}
})
} catch (e) {
console.error(e)
}
},
hexagonDataConverter (data) {
const featureCollection = { type: 'FeatureCollection' }
// 对hexId去重将重复的hexId的时间合并
const hexagons = []
data.forEach(d => {
const find = hexagons.find(h => h.hexId === d.hexId)
if (!find) {
hexagons.push({ hexId: d.hexId, locations: [{ time: d.time, longitude: d.subscriberLongitude, latitude: d.subscriberLatitude }] })
} else {
if (find.locations.length < 6) {
find.locations.push({ time: d.time, longitude: d.subscriberLongitude, latitude: d.subscriberLatitude })
}
}
})
featureCollection.features = hexagons.map((d, i) => ({
id: i + 100000,
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
h3ToGeoBoundary(d.hexId, true)
]
},
properties: {
hexId: d.hexId,
number: d.number,
locations: d.locations,
color: [37, 55, 128]
}
}))
return featureCollection
},
bindTrackingHexagonEvents () {
this.mapChart.on('mouseenter', 'trackingHexagon', this.trackingHexagonMouseEnter)
this.mapChart.on('mouseleave', 'trackingHexagon', this.trackingHexagonMouseLeave)
this.mapChart.on('mousemove', 'trackingHexagon', this.trackingHexagonMouseMove)
},
hoverTrigger (source, id, hover) {
this.mapChart.setFeatureState({ source, id }, { hover })
},
timelineMouseEnter (subscriber, record) {
this.trackingPolygonSourceData.features.forEach(f => {
this.hoverTrigger('trackingHexGrid', f.id, false)
})
if (this.currentShowSubscriber.subscriberId === subscriber.subscriberId) {
const find = this.trackingPolygonSourceData.features.find(d => d.properties.hexId === record.hexId)
if (find) {
this.hoverTrigger('trackingHexGrid', find.id, true)
}
}
},
timelineMouseLeave (subscriber, record) {
if (this.currentShowSubscriber.subscriberId === subscriber.subscriberId) {
const find = this.trackingPolygonSourceData.features.find(d => d.properties.hexId === record.hexId)
if (find) {
this.hoverTrigger('trackingHexGrid', find.id, false)
}
}
},
changeTimeFilterToInitList () {
this.trackingSubscriber.scrollStartIndex = 0
this.trackingSubscriber.scrollEndIndex = 11
this.trackingSubscriber.startOffset = 0
this.trackingSubscriber.listHeight = 0
},
developmentClick () {
const dev = process.env.NODE_ENV
if (dev) {
this.trackingMapLoading = false
}
}
},
setup () {
const dateRangeValue = DEFAULT_TIME_FILTER_RANGE.entity.subscriberMap || 60
const timeFilter = ref({ dateRangeValue })
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = startTime
timeFilter.value.endTime = endTime
const currentPoint = ref({})
const tooltip = ref({
showTooltip: false,
type: ''
})
const myChart = shallowRef({})
const trackingSubscriber = ref({
subscriberId: '',
show: false,
showLine: false,
scrollStartIndex: 0,
scrollEndIndex: 11,
startOffset: 0,
listHeight: 0,
trackRecords: []
})
const trackingSubscriberList = ref([])
const scrollInfo = ref({
itemSize: 50,
containerHeight: 660
})
const trackingNoData = ref(true)
const showTrackingError = ref(false)
const trackingErrorMsg = ref('')
const tooltipDomHeight = {
hexagon: 153,
baseStation: 153,
human: 167
}
const currentPolygon = ref({})
const currentSubscriber = ref({})
const trackingHumanMarker = shallowRef({})
const mapChart = shallowRef(null)
const baseStationMarkers = shallowRef([])
const mapConfig = localStorage.getItem(storageKey.mapConfig) ? JSON.parse(localStorage.getItem(storageKey.mapConfig)) : defaultMapConfig
const trackingMapLoading = ref(true)
return {
timeFilter,
currentPoint,
tooltip,
myChart,
maxZoom: mapConfig.maxZoom, // 地图最小缩放比例
minZoom: mapConfig.minZoom, // 地图最大缩放比例
mapLevel: 1, // 地图精度 1、2、3
defaultZoom: mapConfig.defaultZoom, // 地图默认缩放比例
center: mapConfig.center, // 地图默认中心点。北京:[116.38, 39.9] 纽约:[-73.94539, 40.841843]
trackingSubscriber,
trackingSubscriberList,
scrollInfo,
trackingNoData,
showTrackingError,
trackingErrorMsg,
tooltipDomHeight,
currentPolygon,
currentSubscriber,
trackingHumanMarker,
trackingPolygonSourceData: shallowRef({}),
mapChart,
baseStationMarkers,
trackingMapLoading
}
}
}
</script>

View File

@@ -51,6 +51,10 @@ export default {
className = 'cn-icon cn-icon-app2'
break
}
case ('subscriber_id'): {
className = 'cn-icon cn-icon-pedestrian'
break
}
default: break
}
return className
@@ -120,10 +124,14 @@ export default {
},
methods: {
showDetail () {
let entityType = _.cloneDeep(this.entityData.entityType)
if (entityType === 'subscriber_id') {
entityType = entityType.slice(0, -3)
}
const { href } = this.$router.resolve({
path: '/entity/detail',
query: {
entityType: this.entityData.entityType,
entityType: entityType,
entityName: this.entityData.entityValue,
range: this.timeFilter.dateRangeValue
}
@@ -264,6 +272,10 @@ export default {
this.performanceEventUrl = api.entity.entityList.appEventPerformance
break
}
case 'subscriber_id': {
this.trafficUrl = api.entity.entityList.subscriberThroughput
break
}
default:
break
}
@@ -348,8 +360,10 @@ export default {
this.initUrl()
setTimeout(() => {
this.queryEntityDetailTraffic()
this.queryNetworkQuantity()
this.queryEventNum()
if (this.entity.entityType !== 'subscriber_id') {
this.queryNetworkQuantity()
this.queryEventNum()
}
})
},
beforeUnmount () {

View File

@@ -81,7 +81,7 @@
>
<i class="cn-icon cn-icon-upload2"></i>
<div class="el-upload__text">
<div>{{ $t('knowledgeBase.dropFileHereOr') }}<em>{{ $t('knowledgeBase.clickToUpload') }}</em></div>
<div ref="uploadButton">{{ $t('knowledgeBase.dropFileHereOr') }}<em>{{ $t('knowledgeBase.clickToUpload') }}</em></div>
<div class="upload-tip">{{ $t('knowledgeBase.supportCsv') }}<span @click.stop="downloadTemplate">{{$t('knowledgeBase.downloadTemplate')}}</span></div>
</div>
</el-upload>
@@ -664,15 +664,10 @@ export default {
customClass: 'del-model'
}).then(() => {
this.isClick = true
self.$refs.upload.$refs.uploadRef.handleClick()
document.getElementsByName('file')[0].click()
}).catch(() => {}).finally(() => {
this.isShowUploadTips = false
})
} else {
this.isClick = true
this.isShowUploadTips = false
self.$refs.upload.submit()
self.$refs.upload.$refs.uploadRef.handleClick()
}
}
},
@@ -1196,10 +1191,15 @@ export default {
const div = document.getElementsByClassName('el-upload-dragger')[0]
const self = this
div.addEventListener('click', function (event) {
this.isClick = true
event.stopPropagation()
event.preventDefault()
self.uploadTip(event)
self.isClick = true
if (!self.isShowUploadTips && self.importedData.length === 0) {
self.isClick = true
self.isShowUploadTips = false
} else {
event.stopPropagation()
event.preventDefault()
self.uploadTip(event)
}
})
if (this.knowledgeBaseId) {
this.stepHeights[2] = itemListHeight.hasData// 修改的时候一直是478