CN-1508 feat: subscriber流量曲线图开发
This commit is contained in:
@@ -1,25 +1,25 @@
|
||||
const BASE_CONFIG = {
|
||||
baseUrl: 'http://192.168.44.54:8090/',
|
||||
version: '23.10',
|
||||
version: '23.12',
|
||||
apiVersion: 'v1'
|
||||
}
|
||||
// 默认时间过滤条件,单位分钟. 0表示请求接口时不传时间参数
|
||||
const DEFAULT_TIME_FILTER_RANGE = {
|
||||
dashboard: 60,
|
||||
dashboard: 60, // 所有dashboard
|
||||
entity: {
|
||||
list: 60,
|
||||
trafficLine: 60,
|
||||
subscriberKpi: 60,
|
||||
subscriberTopApp: 60,
|
||||
subscriberMap: 60,
|
||||
informationAggregation: 0,
|
||||
relatedEntity: 60 * 24 * 7,
|
||||
openPort: 60 * 24 * 7,
|
||||
deviceInformation: 60 * 24 * 7,
|
||||
accountInformation: 60 * 24 * 7,
|
||||
securityEvent: 60 * 24 * 7,
|
||||
performanceEvent: 60 * 24 * 7,
|
||||
behaviorPattern: 60 * 24 * 7
|
||||
list: 60, // 实体列表
|
||||
trafficLine: 60, // 实体详情--通用--流量曲线
|
||||
subscriberKpi: 60, // 实体详情--subscriber--kpi
|
||||
subscriberTopApp: 60, // 实体详情--subscriber--topApp
|
||||
subscriberMap: 60, // 实体详情--subscriber--地图
|
||||
informationAggregation: 0, // 实体详情--通用--信息聚合
|
||||
relatedEntity: 60 * 24 * 7, // 实体详情--通用--相关实体
|
||||
openPort: 60 * 24 * 7, // 实体详情--通用--开放端口
|
||||
deviceInformation: 60 * 24 * 7, // 实体详情--subscriber--设备信息
|
||||
accountInformation: 60 * 24 * 7, // 实体详情--subscriber--账户信息
|
||||
securityEvent: 60 * 24 * 7, // 实体详情--通用--安全事件
|
||||
performanceEvent: 60 * 24 * 7, // 实体详情--通用--性能事件
|
||||
behaviorPattern: 60 * 24 * 7 // 实体详情--IP--行为模式
|
||||
},
|
||||
detection: 60
|
||||
detection: 60 // 事件日志列表
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
@import 'views/charts2/EntityDetailSubscriberKpi.scss';
|
||||
@import 'views/charts2/EntityDetailSubscriberTopApp.scss';
|
||||
@import 'views/charts2/entityDetailSubscriberMap.scss';
|
||||
@import 'views/charts2/entityDetailSubscriberLine.scss';
|
||||
|
||||
@import 'views/charts2/entityDetailTabs';
|
||||
@import 'views/charts2/digitalCertificate';
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
.entity-detail-line--subscriber {
|
||||
$blue: #046ECA;
|
||||
$grey: #353636;
|
||||
height:100%;
|
||||
|
||||
.el-tabs__content {
|
||||
overflow: visible;
|
||||
}
|
||||
.cn-chart__tabs {
|
||||
height:100%;
|
||||
.tab-pane {
|
||||
height:100%;
|
||||
}
|
||||
.el-tabs__header {
|
||||
margin-bottom: 10px;
|
||||
width: calc(100% - 272px);
|
||||
}
|
||||
.el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: transparent ;
|
||||
}
|
||||
.el-tabs__nav.is-top {
|
||||
height: 33px;
|
||||
|
||||
.el-tabs__active-bar {
|
||||
background-color: $blue;
|
||||
}
|
||||
.el-tabs__item {
|
||||
padding: 0 10px;
|
||||
height: 33px;
|
||||
color: $grey;
|
||||
font-size: 14px;
|
||||
|
||||
&.el-tabs__item.is-top.is-active {
|
||||
color:$blue;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.el-tabs__content {
|
||||
height: calc(100% - 40px);
|
||||
border:none;
|
||||
.el-table__body-wrapper {
|
||||
height: calc(100% - 45px) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,6 +496,40 @@ export function stackedLineTooltipFormatter (params) {
|
||||
str += '</div>'
|
||||
return str
|
||||
}
|
||||
// subscriber详情页的app页签
|
||||
export function stackedLineWithLegendTooltipFormatter (params) {
|
||||
// 堆叠图tooltip倒叙操作
|
||||
params = params.reverse()
|
||||
|
||||
let str = '<div>'
|
||||
params.forEach((item, i) => {
|
||||
const tData = item.data[0]
|
||||
if (i === 0) {
|
||||
str += '<div style="margin-bottom: 5px">'
|
||||
str += dateFormatByAppearance(tData)
|
||||
str += '</div>'
|
||||
}
|
||||
})
|
||||
str += '<div class="cn-chart-body">'
|
||||
str += '<div class="cn-chart-tooltip">'
|
||||
params.forEach((item, i) => {
|
||||
str += '<span class="cn-chart-tooltip-box">'
|
||||
str += `<span style="display:inline-block;margin-right:4px;border-radius:2px;width:10px;height:10px;background-color:${item.borderColor};"></span>`
|
||||
str += `<span class="cn-chart-tooltip-content">${item.seriesName.split('(')[0]}</span>`
|
||||
str += '</span>'
|
||||
})
|
||||
str += '</div>'
|
||||
str += '<div class="cn-chart-tooltip">'
|
||||
params.forEach((item, i) => {
|
||||
str += `<span class="cn-chart-tooltip-value cn-chart-tooltip__color">
|
||||
${valueToRangeValue(item.data[1], item.value[2]).join(' ')}
|
||||
</span>`
|
||||
})
|
||||
str += '</div>'
|
||||
str += '</div>'
|
||||
str += '</div>'
|
||||
return str
|
||||
}
|
||||
export function appStackedLineTooltipFormatter (params) {
|
||||
let str = '<div>'
|
||||
params.forEach((item, i) => {
|
||||
|
||||
@@ -184,6 +184,12 @@
|
||||
:entity="entity"
|
||||
@toggleLoading="toggleLoading"
|
||||
></entity-detail-line>
|
||||
<entity-detail-subscriber-line
|
||||
v-else-if="chart.type === typeMapping.entityDetail.subscriberLine"
|
||||
:chart="chart"
|
||||
:entity="entity"
|
||||
@toggleLoading="toggleLoading"
|
||||
></entity-detail-subscriber-line>
|
||||
<entity-detail-tabs-chart
|
||||
v-else-if="chart.type === typeMapping.entityDetail.tabsChart"
|
||||
:chart="chart"
|
||||
@@ -228,6 +234,7 @@ import DnsRecentEvents from '@/views/charts2/charts/dnsInsight/DnsRecentEvents'
|
||||
import DnsTrafficLine from '@/views/charts2/charts/dnsInsight/DnsTrafficLine'
|
||||
import EntityDetailBasicInfo from '@/views/charts2/charts/entityDetail/EntityDetailBasicInfo'
|
||||
import EntityDetailLine from '@/views/charts2/charts/entityDetail/EntityDetailLine'
|
||||
import EntityDetailSubscriberLine from '@/views/charts2/charts/entityDetail/EntityDetailSubscriberLine'
|
||||
import EntityDetailSubscriberKpi from '@/views/charts2/charts/entityDetail/EntityDetailSubscriberKpi'
|
||||
import EntityDetailSubscriberTopApp from '@/views/charts2/charts/entityDetail/EntityDetailSubscriberTopApp'
|
||||
import EntityDetailTabsChart from '@/views/charts2/charts/entityDetail/EntityDetailTabs'
|
||||
@@ -269,6 +276,7 @@ export default {
|
||||
EntityDetailSubscriberKpi,
|
||||
EntityDetailSubscriberTopApp,
|
||||
EntityDetailLine,
|
||||
EntityDetailSubscriberLine,
|
||||
EntityDetailTabsChart,
|
||||
EntityDetailMap
|
||||
},
|
||||
|
||||
@@ -37,6 +37,7 @@ export const typeMapping = {
|
||||
subscriberKpi: 714,
|
||||
subscriberTopApp: 715,
|
||||
line: 107,
|
||||
subscriberLine: 108,
|
||||
tabsChart: 713,
|
||||
map: 3
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="entity-detail-line entity-detail-line--subscriber">
|
||||
<el-tabs v-model="lineOuterTab" class="cn-chart__tabs" @tab-click="switchOuterTab">
|
||||
<el-tab-pane name="serviceName" class="tab-pane" :label="$t('subscriber.serviceName')">
|
||||
<service-name
|
||||
v-if="lineOuterTab === 'serviceName'"
|
||||
:chart="chart"
|
||||
:entity="entity"
|
||||
@toggleLoading="toggleLoading"
|
||||
></service-name>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="app" class="tab-pane" label="APP">
|
||||
<app
|
||||
v-if="lineOuterTab === 'app'"
|
||||
:chart="chart"
|
||||
:entity="entity"
|
||||
@toggleLoading="toggleLoading"
|
||||
></app>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ServiceName from '@/views/charts2/charts/entityDetail/lines/ServiceName'
|
||||
import App from '@/views/charts2/charts/entityDetail/lines/App'
|
||||
import {useRoute} from 'vue-router'
|
||||
import {ref} from 'vue'
|
||||
import {overwriteUrl, urlParamsHandler} from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
name: 'EntityDetailLine',
|
||||
components: {
|
||||
ServiceName,
|
||||
App
|
||||
},
|
||||
props: {
|
||||
chart: Object,
|
||||
entity: Object
|
||||
},
|
||||
methods: {
|
||||
switchOuterTab (tab) {
|
||||
const { query } = this.$route
|
||||
const newUrl = urlParamsHandler(window.location.href, query, {
|
||||
lineOuterTab: tab.paneName
|
||||
})
|
||||
overwriteUrl(newUrl)
|
||||
},
|
||||
toggleLoading (loading) {
|
||||
this.$emit('toggleLoading', loading)
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
const { query } = useRoute()
|
||||
const lineOuterTab = ref(query.lineOuterTab || 'serviceName')
|
||||
return {
|
||||
lineOuterTab
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
653
src/views/charts2/charts/entityDetail/lines/App.vue
Normal file
653
src/views/charts2/charts/entityDetail/lines/App.vue
Normal file
@@ -0,0 +1,653 @@
|
||||
<template>
|
||||
<div class="line network">
|
||||
<chart-error v-if="showError" :content="errorMsg"/>
|
||||
<div class="line-header" v-if="!showError">
|
||||
<div class="line-header-left">
|
||||
<div class="line-value-active" v-if="lineTab"></div>
|
||||
<div class="line-value">
|
||||
<template v-for="(item, index) in tabs">
|
||||
<div class="line-value-tabs"
|
||||
:class=" {'is-active': lineTab === item.class, 'mousemove-cursor': mousemoveCursor === item.class}"
|
||||
v-if="item.show"
|
||||
:key="index"
|
||||
@mouseenter="mouseenter(item)"
|
||||
@mouseleave="mouseleave(item)"
|
||||
@click="activeChange(item, index,true)"
|
||||
:test-id="`tab${index}`"
|
||||
>
|
||||
<div class="line-value-tabs-name">
|
||||
<div class="tabs-name" :test-id="`tabTitle${index}`" style="padding-left: 0;">{{ $t(item.name) }}</div>
|
||||
</div>
|
||||
<div class="line-value-unit" :test-id="`tabContent${index}`">
|
||||
<span class="line-value-unit-number">
|
||||
{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[0] }}
|
||||
</span>
|
||||
<span class="line-value-unit-number2">
|
||||
<span>{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[1] }}</span>
|
||||
<span v-if="item.unitType">{{ item.unitType }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-select line-header-right">
|
||||
<div class="panel__tools">
|
||||
<div class="panel__time line-date-right">
|
||||
<date-time-range
|
||||
class="entity-detail-date-time-range"
|
||||
:start-time="timeFilter.startTime"
|
||||
:end-time="timeFilter.endTime"
|
||||
:date-range="timeFilter.dateRangeValue"
|
||||
ref="dateTimeRange"
|
||||
@change="reload"
|
||||
/>
|
||||
</div>
|
||||
<div class="line-select-metric">
|
||||
<span class="select-prefix line-margin-right">{{$t('network.metric')}}:</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="metric"
|
||||
placeholder=" "
|
||||
popper-class="common-select"
|
||||
:popper-append-to-body="false"
|
||||
@change="metricChange"
|
||||
>
|
||||
<el-option v-for="item in metricOptions" :key="item.value" :label="$t(item.label)" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: calc(100% - 74px); position: relative">
|
||||
<chart-no-data v-if="isNoData && !showError"></chart-no-data>
|
||||
<div class="chart-drawing" v-show="!isNoData && !showError" ref="overviewLineChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import chartMixin from '@/views/charts2/chart-mixin'
|
||||
import * as echarts from 'echarts'
|
||||
import { stackedLineWithLegendChartOption } from '@/views/charts2/charts/options/echartOption'
|
||||
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
|
||||
import { unitTypes, chartColor3, chartColor4, metricType, metricOptions } from '@/utils/constants.js'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { stackedLineWithLegendTooltipFormatter } from '@/views/charts/charts/tools'
|
||||
import _ from 'lodash'
|
||||
import axios from 'axios'
|
||||
import { api } from '@/utils/api'
|
||||
import { getNowTime, getSecond } from '@/utils/date-util'
|
||||
import ChartNoData from '@/views/charts/charts/ChartNoData'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getLineType, overwriteUrl, urlParamsHandler } from '@/utils/tools'
|
||||
import ChartError from '@/components/common/Error'
|
||||
import { dataForNetworkOverviewLine } from '@/utils/static-data'
|
||||
|
||||
export default {
|
||||
name: 'EntityDetailLine',
|
||||
mixins: [chartMixin],
|
||||
components: {
|
||||
ChartError,
|
||||
ChartNoData
|
||||
},
|
||||
setup () {
|
||||
const { query } = useRoute()
|
||||
const lineTab = ref(query.lineTab || 'total')
|
||||
const queryCondition = ref(query.queryCondition || '')
|
||||
const tabOperationType = ref(query.tabOperationType)
|
||||
const networkOverviewBeforeTab = ref(query.networkOverviewBeforeTab)
|
||||
const metric = ref(query.metric || 'Bits/s')
|
||||
|
||||
// 获取url携带的range、startTime、endTime
|
||||
const rangeParam = query.range
|
||||
const startTimeParam = query.startTime
|
||||
const endTimeParam = query.endTime
|
||||
|
||||
// 优先级:url > config.js > 默认值。
|
||||
const dateRangeValue = rangeParam ? parseInt(rangeParam) : (DEFAULT_TIME_FILTER_RANGE.entity.trafficLine || 60)
|
||||
const timeFilter = ref({ dateRangeValue })
|
||||
if (!startTimeParam || !endTimeParam) {
|
||||
const { startTime, endTime } = getNowTime(dateRangeValue)
|
||||
timeFilter.value.startTime = startTime
|
||||
timeFilter.value.endTime = endTime
|
||||
} else {
|
||||
timeFilter.value.startTime = parseInt(startTimeParam)
|
||||
timeFilter.value.endTime = parseInt(endTimeParam)
|
||||
}
|
||||
|
||||
return {
|
||||
lineTab,
|
||||
queryCondition,
|
||||
tabOperationType,
|
||||
networkOverviewBeforeTab,
|
||||
myChart: shallowRef(null),
|
||||
metric,
|
||||
timeFilter
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabsTemplate: dataForNetworkOverviewLine.tabsTemplate,
|
||||
tabs: [],
|
||||
unitConvert,
|
||||
valueToRangeValue,
|
||||
unitTypes,
|
||||
chartDateObject: [],
|
||||
timer: null,
|
||||
mousemoveCursor: '',
|
||||
leftOffset: 0,
|
||||
sizes: [3, 4, 6, 8, 9, 10],
|
||||
dynamicVariable: '',
|
||||
mouseDownFlag: false,
|
||||
brushHistory: [],
|
||||
showError: false,
|
||||
errorMsg: '',
|
||||
metricOptions
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
lineTab (n) {
|
||||
this.$nextTick(() => {
|
||||
this.handleActiveBar(n)
|
||||
this.reloadUrl({ lineTab: n })
|
||||
})
|
||||
},
|
||||
timeFilter: {
|
||||
handler () {
|
||||
if (this.lineTab) {
|
||||
this.init(this.metric, 'active')
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
metric (n) {
|
||||
this.handleActiveBar()
|
||||
if (n === metricType.Sessions) {
|
||||
this.lineTab = 'total'
|
||||
}
|
||||
this.init(n, 'active')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reloadUrl (newParam) {
|
||||
const { query } = this.$route
|
||||
const newUrl = urlParamsHandler(window.location.href, query, newParam)
|
||||
overwriteUrl(newUrl)
|
||||
},
|
||||
init (val, active) {
|
||||
const newVal = val ? _.clone(val) : this.metric
|
||||
|
||||
const params = {
|
||||
resource: this.entity.entityName,
|
||||
startTime: getSecond(this.timeFilter.startTime),
|
||||
endTime: getSecond(this.timeFilter.endTime),
|
||||
metric: newVal
|
||||
}
|
||||
|
||||
if (this.queryCondition) {
|
||||
params.q = this.queryCondition
|
||||
}
|
||||
|
||||
this.toggleLoading(true)
|
||||
axios.get(`${api.entity.throughput}/${this.entity.entityType}/relate/app`, { params: params }).then(response => {
|
||||
const res = response.data
|
||||
|
||||
if (response.status === 200) {
|
||||
this.isNoData = res.data.result.length === 0
|
||||
this.showError = false
|
||||
if (!active) {
|
||||
this.tabs = _.cloneDeep(this.tabsTemplate)
|
||||
}
|
||||
if (this.isNoData) {
|
||||
this.lineTab = ''
|
||||
this.tabs = _.cloneDeep(this.tabsTemplate)
|
||||
} else {
|
||||
this.initData(res.data.result, newVal, active)
|
||||
}
|
||||
} else {
|
||||
this.httpError(res)
|
||||
}
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.httpError(e)
|
||||
}).finally(() => {
|
||||
this.toggleLoading(false)
|
||||
})
|
||||
},
|
||||
echartsInit (echartsData) {
|
||||
// echarts内容在单元测试时不执行
|
||||
if (!this.isUnitTesting) {
|
||||
if (this.lineTab) {
|
||||
this.handleActiveBar()
|
||||
}
|
||||
const _this = this
|
||||
this.chartOption = stackedLineWithLegendChartOption
|
||||
const chartOption = this.chartOption.series[0]
|
||||
this.chartOption.series = echartsData.map((t, i) => {
|
||||
return {
|
||||
...chartOption,
|
||||
name: t.name,
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
lineStyle: {
|
||||
color: chartColor3[i],
|
||||
width: 1
|
||||
},
|
||||
stack: 'stack',
|
||||
symbolSize: function (value) {
|
||||
return _this.symbolSizeSortChange(i, value[0])
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
borderColor: chartColor4[i],
|
||||
borderWidth: 2,
|
||||
shadowColor: chartColor4[i]
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.1,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: chartColor3[i]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: chartColor3[i]
|
||||
}
|
||||
])
|
||||
},
|
||||
data: t.values.map(v => [Number(v[0]) * 1000, Number(v[1]), 'number'])
|
||||
}
|
||||
})
|
||||
|
||||
this.chartOption.tooltip.formatter = (params) => {
|
||||
params.forEach(t => {
|
||||
echartsData.forEach((e, i) => {
|
||||
if (e.name === t.seriesName) {
|
||||
t.borderColor = chartColor3[i]
|
||||
}
|
||||
})
|
||||
})
|
||||
return stackedLineWithLegendTooltipFormatter(params)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose()
|
||||
}
|
||||
this.myChart = echarts.init(this.$refs.overviewLineChart)
|
||||
this.myChart.setOption(this.chartOption)
|
||||
|
||||
this.myChart.dispatchAction({
|
||||
type: 'takeGlobalCursor',
|
||||
key: 'brush',
|
||||
brushOption: {
|
||||
brushType: 'lineX',
|
||||
xAxisIndex: 'all',
|
||||
brushMode: 'single',
|
||||
throttleType: 'debounce'
|
||||
}
|
||||
})
|
||||
|
||||
this.myChart.on('brushEnd', this.brushEcharts)
|
||||
})
|
||||
}
|
||||
},
|
||||
activeChange (item, index, isClick) { // isClick:代表是通过点击操作来的
|
||||
if (this.isNoData) return
|
||||
if (this.lineTab === item.class) return
|
||||
this.lineTab = item.class
|
||||
this.legendSelectChange(item, index, 'active')
|
||||
this.init(this.metric, 'active')
|
||||
},
|
||||
mouseenter (item) {
|
||||
if (this.isNoData) return
|
||||
this.mousemoveCursor = item.class
|
||||
this.handleActiveBar(item.class)
|
||||
},
|
||||
mouseleave () {
|
||||
this.mousemoveCursor = ''
|
||||
},
|
||||
dispatchSelectAction (type, name) {
|
||||
this.myChart && this.myChart.dispatchAction({
|
||||
type: type,
|
||||
name: name
|
||||
})
|
||||
},
|
||||
legendSelectChange (item, index, val, isActiveAll) {
|
||||
if (index === 'index') {
|
||||
this.dispatchSelectAction('legendSelect', item.name)
|
||||
} else if (this.tabs[index] && this.tabs[index].name === item.name) {
|
||||
if (isActiveAll) {
|
||||
this.tabs.forEach((t) => {
|
||||
this.dispatchSelectAction('legendSelect', t.name)
|
||||
})
|
||||
} else {
|
||||
this.dispatchSelectAction('legendSelect', item.name)
|
||||
this.tabs.forEach((t) => {
|
||||
if (t.name !== item.name) {
|
||||
this.dispatchSelectAction('legendUnSelect', t.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (val === 'active') {
|
||||
this.tabs.forEach(t => {
|
||||
t.invertTab = item.name === t.name ? !t.invertTab : true
|
||||
if (t.invertTab && item.name === t.name) {
|
||||
this.lineTab = this.lineTab ? '' : t.class
|
||||
this.tabs.forEach((e) => {
|
||||
this.dispatchSelectAction('legendSelect', e.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
handleActiveBar () {
|
||||
if (document.querySelector('.network .line-value-tabs.is-active')) {
|
||||
const {
|
||||
offsetLeft,
|
||||
clientWidth,
|
||||
clientLeft
|
||||
} = document.querySelector('.network .line-value-tabs.is-active')
|
||||
const activeBar = document.querySelector('.network .line-value-active')
|
||||
activeBar.style.cssText += `width: ${clientWidth}px; left: ${offsetLeft + this.leftOffset + clientLeft}px;`
|
||||
}
|
||||
},
|
||||
resize () {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize()
|
||||
}
|
||||
},
|
||||
symbolSizeSortChange (index, time) {
|
||||
const dataIntegrationArray = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (stackedLineWithLegendChartOption.series[i]) {
|
||||
const item = stackedLineWithLegendChartOption.series[i].data.find(t => t[0] === time)
|
||||
if (item) {
|
||||
dataIntegrationArray.push(item)
|
||||
item[2] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataIntegrationArray.sort((a, b) => {
|
||||
return a[1] - b[1]
|
||||
})
|
||||
const sortIndex = dataIntegrationArray.findIndex(a => a[2] === index)
|
||||
return this.sizes[sortIndex]
|
||||
},
|
||||
initData (data, val, active) {
|
||||
const lineData = []
|
||||
const tabData = []
|
||||
|
||||
if (data && data.length > 0) {
|
||||
data.forEach(app => {
|
||||
Object.keys(app).forEach(appName => {
|
||||
if (app[appName] && app[appName].length > 0) {
|
||||
app[appName].forEach(item => {
|
||||
// 整理tab数据
|
||||
const templateList = ['total', 'inbound', 'outbound', 'internal', 'through', 'other']
|
||||
let typeData = tabData.find(t => t.type === item.type)
|
||||
if (!typeData) {
|
||||
tabData.push({
|
||||
type: item.type
|
||||
})
|
||||
typeData = tabData.find(t => t.type === item.type)
|
||||
}
|
||||
for (const key in templateList) {
|
||||
const code = Object.keys(item).find(d => d.indexOf(templateList[key]) > -1)
|
||||
if (code) {
|
||||
if (typeData[code]) {
|
||||
typeData[code].analysis.avg += Number(item[code].analysis.avg)
|
||||
typeData[code].analysis.min += Number(item[code].analysis.min)
|
||||
typeData[code].analysis.max += Number(item[code].analysis.max)
|
||||
typeData[code].analysis.p95 += Number(item[code].analysis.p95)
|
||||
} else {
|
||||
typeData[code] = {
|
||||
analysis: {
|
||||
avg: Number(item[code].analysis.avg),
|
||||
min: Number(item[code].analysis.min),
|
||||
max: Number(item[code].analysis.max),
|
||||
p95: Number(item[code].analysis.p95)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 整理line数据
|
||||
if (templateList[key] === this.lineTab && this.metric === getLineType(item.type)) {
|
||||
lineData.push({ name: appName, ...item[code] })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
tabData.forEach(t => {
|
||||
t.type = getLineType(t.type)
|
||||
})
|
||||
// TODO 下面的逻辑是判断曲线的尾部数据,每条曲线从尾往前数0值的个数,若每条曲线的值全部为0个数大于0,所有曲线都从尾部去掉相同数量的点,最多4个
|
||||
const zeroPointCount = []
|
||||
lineData.forEach(l => {
|
||||
if (_.get(l, 'values', []).length > 4) {
|
||||
let count = 0
|
||||
for (let i = l.values.length - 1; i >= l.values.length - 4; i--) {
|
||||
if (l.values[i].length > 1 && l.values[i][1] === 0) {
|
||||
count++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
zeroPointCount.push(count)
|
||||
}
|
||||
})
|
||||
if (zeroPointCount.length > 0) {
|
||||
const minCount = _.min(zeroPointCount)
|
||||
if (minCount > 0) {
|
||||
lineData.forEach(l => {
|
||||
l.values.splice(l.values.length - minCount, minCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (val === metricType.Sessions) {
|
||||
const tabs = _.cloneDeep(this.tabsTemplate)
|
||||
tabData.forEach((t, i) => {
|
||||
if (this.metric === t.type) {
|
||||
tabs[i].analysis = t.analysis
|
||||
}
|
||||
})
|
||||
tabs.forEach((tab, i) => {
|
||||
if (i !== 0) {
|
||||
tab.show = false
|
||||
}
|
||||
tab.invertTab = false
|
||||
tabData.forEach((t, i) => {
|
||||
if (this.metric === t.type) {
|
||||
Object.keys(t).forEach(k => {
|
||||
if (k !== 'type' && k.indexOf(tab.class) > -1) {
|
||||
tab.analysis = t[k].analysis
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
const metric = metricOptions.find(item => item.value === metricType.Sessions)
|
||||
tab.unitType = metric ? this.$t(metric.label) : ''
|
||||
this.lineTab = 'total'
|
||||
this.legendSelectChange(tab, 0)
|
||||
})
|
||||
/*tabs.forEach((e, i) => {
|
||||
if (i !== 0) {
|
||||
e.show = false
|
||||
}
|
||||
const metric = metricOptions.find(item => item.value === metricType.Sessions)
|
||||
e.unitType = metric ? this.$t(metric.label) : ''
|
||||
e.invertTab = false
|
||||
this.lineTab = 'total'
|
||||
this.legendSelectChange(e, 0)
|
||||
})*/
|
||||
this.tabs = tabs
|
||||
this.$nextTick(() => {
|
||||
this.echartsInit(lineData)
|
||||
})
|
||||
} else {
|
||||
let metric = metricOptions.find(item => item.value === val)
|
||||
if (!metric) {
|
||||
metric = metricOptions.find(item => item.value === metricType.Packets)
|
||||
}
|
||||
const unit = metric ? this.$t(metric.label) : ''
|
||||
this.legendInit(tabData, lineData, active, unit, val)
|
||||
}
|
||||
},
|
||||
legendInit (tabData, lineData, active, type, val) {
|
||||
const tabs = _.cloneDeep(this.tabsTemplate)
|
||||
tabs.forEach(tab => {
|
||||
tabData.forEach((t, i) => {
|
||||
if (this.metric === t.type) {
|
||||
Object.keys(t).forEach(k => {
|
||||
if (k !== 'type' && k.indexOf(tab.class) > -1) {
|
||||
tab.analysis = t[k].analysis
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
let num = 0
|
||||
const self = this
|
||||
tabs.forEach(e => {
|
||||
e.unitType = type
|
||||
if (e.name !== 'network.total' && parseFloat(e.analysis.max) === 0) {
|
||||
e.show = false
|
||||
num += 1
|
||||
} else {
|
||||
e.show = true
|
||||
if (!active) {
|
||||
self.legendSelectChange(e, 'index')
|
||||
}
|
||||
}
|
||||
if (self.lineTab === e.class) {
|
||||
if (parseFloat(e.analysis.max) <= 0) {
|
||||
self.lineTab = ''
|
||||
// self.init() // 后续多测试关注
|
||||
}
|
||||
}
|
||||
})
|
||||
const emptyData = tabs.filter(d => parseFloat(d.analysis.max) === 0)
|
||||
this.isNoData = emptyData.length === tabs.length
|
||||
if (this.isNoData) {
|
||||
return true
|
||||
}
|
||||
this.tabs = tabs
|
||||
if (num === 5) {
|
||||
tabs[0].invertTab = false
|
||||
this.lineTab = 'total'
|
||||
this.legendSelectChange(tabs[0], 0)
|
||||
this.$nextTick(() => {
|
||||
this.echartsInit(lineData)
|
||||
})
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
this.echartsInit(lineData)
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* echarts框选
|
||||
* @param params
|
||||
*/
|
||||
brushEcharts (params) {
|
||||
this.myChart.dispatchAction({
|
||||
type: 'brush',
|
||||
areas: [] // 删除选框
|
||||
})
|
||||
if (!this.mouseDownFlag) {
|
||||
// 避免点击空白区域报错
|
||||
if (params.areas && params.areas.length > 0) {
|
||||
this.brushHistory.unshift({
|
||||
startTime: _.cloneDeep(this.timeFilter.startTime) * 1000,
|
||||
endTime: _.cloneDeep(this.timeFilter.endTime) * 1000
|
||||
})
|
||||
|
||||
const rangeObj = {
|
||||
startTime: Math.ceil(params.areas[0].coordRange[0]),
|
||||
endTime: Math.ceil(params.areas[0].coordRange[1])
|
||||
}
|
||||
|
||||
if (rangeObj.endTime - rangeObj.startTime < 5 * 60 * 1000) {
|
||||
rangeObj.startTime = rangeObj.endTime - 5 * 60 * 1000
|
||||
}
|
||||
this.$store.commit('setRangeEchartsData', rangeObj)
|
||||
}
|
||||
}
|
||||
},
|
||||
httpError (e) {
|
||||
this.isNoData = false
|
||||
this.showError = true
|
||||
this.errorMsg = this.errorMsgHandler(e)
|
||||
},
|
||||
metricChange (value) {
|
||||
const { query } = this.$route
|
||||
const newUrl = urlParamsHandler(window.location.href, query, {
|
||||
metric: value
|
||||
})
|
||||
overwriteUrl(newUrl)
|
||||
},
|
||||
reload (startTime, endTime, dateRangeValue) {
|
||||
this.timeFilter = { startTime: getSecond(startTime), endTime: getSecond(endTime) }
|
||||
const { query } = this.$route
|
||||
// 因为详情页是打开的新标签页,设置时间并不会影响到其他界面,所以store的名称不必改变
|
||||
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)
|
||||
},
|
||||
timeRefreshChange () {
|
||||
// 不是自选时间
|
||||
if (this.$refs.dateTimeRange) {
|
||||
if (!this.$refs.dateTimeRange.isCustom) {
|
||||
const value = this.timeFilter.dateRangeValue
|
||||
this.$refs.dateTimeRange.quickChange(value)
|
||||
} else {
|
||||
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
|
||||
}
|
||||
} else {
|
||||
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.myChart = null
|
||||
this.chartOption = null
|
||||
this.timer = setTimeout(() => {
|
||||
this.init()
|
||||
}, 200)
|
||||
window.addEventListener('resize', this.resize)
|
||||
},
|
||||
beforeUnmount () {
|
||||
clearTimeout(this.timer)
|
||||
window.removeEventListener('resize', this.resize)
|
||||
|
||||
let myChart = echarts.getInstanceByDom(this.$refs.overviewLineChart)
|
||||
if (myChart) {
|
||||
echarts.dispose(myChart)
|
||||
}
|
||||
if (this.myChart) {
|
||||
echarts.dispose(this.myChart)
|
||||
}
|
||||
// 检测时发现该方法占用较大内存,且未被释放
|
||||
this.unitConvert = null
|
||||
this.valueToRangeValue = null
|
||||
myChart = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
720
src/views/charts2/charts/entityDetail/lines/ServiceName.vue
Normal file
720
src/views/charts2/charts/entityDetail/lines/ServiceName.vue
Normal file
@@ -0,0 +1,720 @@
|
||||
<template>
|
||||
<div class="line network">
|
||||
<chart-error v-if="showError" :content="errorMsg"/>
|
||||
<div class="line-header" v-if="!showError">
|
||||
<div class="line-header-left">
|
||||
<div class="line-value-active" v-if="lineTab"></div>
|
||||
<div class="line-value">
|
||||
<template v-for="(item, index) in tabs">
|
||||
<div class="line-value-tabs"
|
||||
:class=" {'is-active': lineTab === item.class, 'mousemove-cursor': mousemoveCursor === item.class}"
|
||||
v-if="item.show"
|
||||
:key="index"
|
||||
@mouseenter="mouseenter(item)"
|
||||
@mouseleave="mouseleave(item)"
|
||||
@click="activeChange(item, index,true)"
|
||||
:test-id="`tab${index}`"
|
||||
>
|
||||
<div class="line-value-tabs-name">
|
||||
<div :class="item.class"></div>
|
||||
<div class="tabs-name" :test-id="`tabTitle${index}`">{{ $t(item.name) }}</div>
|
||||
</div>
|
||||
<div class="line-value-unit" :test-id="`tabContent${index}`">
|
||||
<span class="line-value-unit-number">
|
||||
{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[0] }}
|
||||
</span>
|
||||
<span class="line-value-unit-number2">
|
||||
<span>{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[1] }}</span>
|
||||
<span v-if="item.unitType">{{ item.unitType }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-select line-header-right">
|
||||
<div class="panel__tools">
|
||||
<div class="panel__time line-date-right">
|
||||
<date-time-range
|
||||
class="entity-detail-date-time-range"
|
||||
:start-time="timeFilter.startTime"
|
||||
:end-time="timeFilter.endTime"
|
||||
:date-range="timeFilter.dateRangeValue"
|
||||
ref="dateTimeRange"
|
||||
@change="reload"
|
||||
/>
|
||||
</div>
|
||||
<div class="line-select-metric">
|
||||
<span class="select-prefix line-margin-right">{{$t('network.metric')}}:</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="metric"
|
||||
placeholder=" "
|
||||
popper-class="common-select"
|
||||
:popper-append-to-body="false"
|
||||
@change="metricChange"
|
||||
>
|
||||
<el-option v-for="item in metricOptions" :key="item.value" :label="$t(item.label)" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-select-reference-line">
|
||||
<span class="line-margin-right">{{ $t('network.referenceLine') }}:</span>
|
||||
<div class="line-select__operation">
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="lineRefer"
|
||||
:disabled="!lineTab"
|
||||
placeholder=" "
|
||||
popper-class="common-select"
|
||||
:popper-append-to-body="false"
|
||||
@change="referenceSelectChange"
|
||||
>
|
||||
<el-option :key="options2[0].value" :label="$t(options2[0].label)" :value="options2[0].value"></el-option>
|
||||
<el-option :key="options2[1].value" :label="$t(options2[1].label[0], options2[1].label[1])" :value="options2[1].value"></el-option>
|
||||
<el-option :key="options2[2].value" :label="$t(options2[2].label)" :value="options2[2].value"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: calc(100% - 74px); position: relative">
|
||||
<chart-no-data v-if="isNoData && !showError"></chart-no-data>
|
||||
<div class="chart-drawing" v-show="showMarkLine && !isNoData && !showError" ref="overviewLineChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import chartMixin from '@/views/charts2/chart-mixin'
|
||||
import * as echarts from 'echarts'
|
||||
import { stackedLineChartOption } from '@/views/charts2/charts/options/echartOption'
|
||||
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
|
||||
import { unitTypes, chartColor3, chartColor4, metricType, metricOptions } from '@/utils/constants.js'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { stackedLineTooltipFormatter } from '@/views/charts/charts/tools'
|
||||
import _ from 'lodash'
|
||||
import axios from 'axios'
|
||||
import { api } from '@/utils/api'
|
||||
import { getNowTime, getSecond } from '@/utils/date-util'
|
||||
import ChartNoData from '@/views/charts/charts/ChartNoData'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getLineType, getMarkLineByLineRefer, overwriteUrl, urlParamsHandler } from '@/utils/tools'
|
||||
import ChartError from '@/components/common/Error'
|
||||
import { dataForNetworkOverviewLine } from '@/utils/static-data'
|
||||
|
||||
export default {
|
||||
name: 'EntityDetailLine',
|
||||
mixins: [chartMixin],
|
||||
components: {
|
||||
ChartError,
|
||||
ChartNoData
|
||||
},
|
||||
setup () {
|
||||
const { query } = useRoute()
|
||||
const lineRefer = ref(query.lineRefer || 'Average')
|
||||
const lineTab = ref(query.lineTab || '')
|
||||
const queryCondition = ref(query.queryCondition || '')
|
||||
const tabOperationType = ref(query.tabOperationType)
|
||||
const networkOverviewBeforeTab = ref(query.networkOverviewBeforeTab)
|
||||
const metric = ref(query.metric || 'Bits/s')
|
||||
|
||||
// 获取url携带的range、startTime、endTime
|
||||
const rangeParam = query.range
|
||||
const startTimeParam = query.startTime
|
||||
const endTimeParam = query.endTime
|
||||
|
||||
// 优先级:url > config.js > 默认值。
|
||||
const dateRangeValue = rangeParam ? parseInt(rangeParam) : (DEFAULT_TIME_FILTER_RANGE.entity.trafficLine || 60)
|
||||
const timeFilter = ref({ dateRangeValue })
|
||||
if (!startTimeParam || !endTimeParam) {
|
||||
const { startTime, endTime } = getNowTime(dateRangeValue)
|
||||
timeFilter.value.startTime = startTime
|
||||
timeFilter.value.endTime = endTime
|
||||
} else {
|
||||
timeFilter.value.startTime = parseInt(startTimeParam)
|
||||
timeFilter.value.endTime = parseInt(endTimeParam)
|
||||
}
|
||||
|
||||
return {
|
||||
lineRefer,
|
||||
lineTab,
|
||||
queryCondition,
|
||||
tabOperationType,
|
||||
networkOverviewBeforeTab,
|
||||
myChart: shallowRef(null),
|
||||
metric,
|
||||
timeFilter
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
options2: dataForNetworkOverviewLine.options2,
|
||||
tabsTemplate: dataForNetworkOverviewLine.tabsTemplate,
|
||||
tabs: [],
|
||||
unitConvert,
|
||||
valueToRangeValue,
|
||||
unitTypes,
|
||||
chartDateObject: [],
|
||||
timer: null,
|
||||
mousemoveCursor: '',
|
||||
leftOffset: 0,
|
||||
sizes: [3, 4, 6, 8, 9, 10],
|
||||
dynamicVariable: '',
|
||||
showMarkLine: true,
|
||||
mouseDownFlag: false,
|
||||
brushHistory: [],
|
||||
showError: false,
|
||||
errorMsg: '',
|
||||
metricOptions
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
lineTab (n) {
|
||||
this.$nextTick(() => {
|
||||
this.handleActiveBar(n)
|
||||
this.reloadUrl({ lineTab: n })
|
||||
})
|
||||
},
|
||||
lineRefer (n) {
|
||||
this.reloadUrl({ lineRefer: n })
|
||||
},
|
||||
timeFilter: {
|
||||
handler () {
|
||||
if (this.lineTab) {
|
||||
this.init(this.metric, this.showMarkLine, 'active')
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
metric (n) {
|
||||
this.handleActiveBar()
|
||||
this.showMarkLine = !this.showMarkLine
|
||||
this.tabs.forEach((e) => {
|
||||
if (!e.invertTab) {
|
||||
e.invertTab = true
|
||||
}
|
||||
})
|
||||
this.init(n, this.showMarkLine, '', n)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reloadUrl (newParam) {
|
||||
const { query } = this.$route
|
||||
const newUrl = urlParamsHandler(window.location.href, query, newParam)
|
||||
overwriteUrl(newUrl)
|
||||
},
|
||||
init (val, show, active, n) {
|
||||
const newVal = val ? _.clone(val) : this.metric
|
||||
|
||||
const params = {
|
||||
resource: this.entity.entityName,
|
||||
startTime: getSecond(this.timeFilter.startTime),
|
||||
endTime: getSecond(this.timeFilter.endTime),
|
||||
metric: newVal
|
||||
}
|
||||
|
||||
if (this.queryCondition) {
|
||||
params.q = this.queryCondition
|
||||
}
|
||||
|
||||
this.toggleLoading(true)
|
||||
axios.get(`${api.entity.throughput}/${this.entity.entityType}`, { params: params }).then(response => {
|
||||
const res = response.data
|
||||
|
||||
if (response.status === 200) {
|
||||
this.isNoData = res.data.result.length === 0
|
||||
this.showError = false
|
||||
if (!active) {
|
||||
this.tabs = _.cloneDeep(this.tabsTemplate)
|
||||
}
|
||||
if (this.isNoData) {
|
||||
this.lineTab = ''
|
||||
this.tabs = _.cloneDeep(this.tabsTemplate)
|
||||
} else {
|
||||
this.initData(res.data.result, newVal, active, show, n)
|
||||
}
|
||||
} else {
|
||||
this.httpError(res)
|
||||
}
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.httpError(e)
|
||||
}).finally(() => {
|
||||
this.toggleLoading(false)
|
||||
})
|
||||
},
|
||||
echartsInit (echartsData, show) {
|
||||
// echarts内容在单元测试时不执行
|
||||
if (!this.isUnitTesting) {
|
||||
if (this.lineTab) {
|
||||
this.handleActiveBar()
|
||||
echartsData = echartsData.filter(t => t.show === true && t.class === this.lineTab) // t.invertTab === false
|
||||
} else {
|
||||
echartsData = echartsData.filter(t => t.show === true)
|
||||
}
|
||||
const _this = this
|
||||
this.chartOption = stackedLineChartOption
|
||||
const chartOption = this.chartOption.series[0]
|
||||
echartsData = echartsData.reverse()
|
||||
this.chartOption.series = echartsData.map((t, i) => {
|
||||
return {
|
||||
...chartOption,
|
||||
name: t.name,
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
lineStyle: {
|
||||
color: chartColor3[t.positioning],
|
||||
width: 1
|
||||
},
|
||||
stack: t.name !== 'network.total' ? 'network.total' : '',
|
||||
symbolSize: function (value) {
|
||||
return _this.symbolSizeSortChange(i, value[0])
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
borderColor: chartColor4[t.positioning],
|
||||
borderWidth: 2,
|
||||
shadowColor: chartColor4[t.positioning],
|
||||
shadowBlur: this.sizes[t.positioning] + 2
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.1,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: chartColor3[t.positioning]
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: chartColor3[t.positioning]
|
||||
}
|
||||
])
|
||||
},
|
||||
data: t.data.map(v => [Number(v[0]) * 1000, Number(v[1]), 'number']),
|
||||
markLine: {
|
||||
silent: true,
|
||||
lineStyle: {
|
||||
color: '#B4B1A8'
|
||||
},
|
||||
symbol: 'none',
|
||||
label: {
|
||||
formatter (params) {
|
||||
const arr = unitConvert(params.value, unitTypes.number).join('')
|
||||
const referIndex = _this.options2.findIndex(o => o.value === _this.lineRefer)
|
||||
if (referIndex > -1) {
|
||||
if (referIndex === 1) {
|
||||
return _this.$t(_this.options2[1].label[0], _this.options2[1].label[1]) + '(' + arr + echartsData[0].unitType + ')'
|
||||
} else {
|
||||
return _this.$t(_this.options2[referIndex].label) + '(' + arr + echartsData[0].unitType + ')'
|
||||
}
|
||||
}
|
||||
return _this.lineRefer + '(' + arr + echartsData[0].unitType + ')'
|
||||
},
|
||||
position: 'insideStartTop',
|
||||
color: '#717171',
|
||||
fontFamily: 'NotoSansSChineseRegular'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!show && !this.lineTab) {
|
||||
this.chartOption.series.forEach((t) => {
|
||||
t.markLine.label.show = false
|
||||
t.markLine = []
|
||||
})
|
||||
}
|
||||
|
||||
if (show) {
|
||||
this.chartOption.series.forEach((t, i) => {
|
||||
t.markLine.label.show = true
|
||||
t.markLine.data = [
|
||||
{
|
||||
yAxis: echartsData[i].analysis[getMarkLineByLineRefer(this.lineRefer)]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
this.chartOption.tooltip.formatter = (params) => {
|
||||
params.forEach(t => {
|
||||
t.seriesName = this.$t(t.seriesName)
|
||||
this.tabs.forEach(e => {
|
||||
if (this.$t(e.name) === t.seriesName) {
|
||||
t.borderColor = chartColor3[e.positioning]
|
||||
}
|
||||
})
|
||||
})
|
||||
return stackedLineTooltipFormatter(params)
|
||||
}
|
||||
this.showMarkLine = true
|
||||
this.$nextTick(() => {
|
||||
if (this.myChart) {
|
||||
this.myChart.dispose()
|
||||
}
|
||||
this.myChart = echarts.init(this.$refs.overviewLineChart)
|
||||
this.myChart.setOption(this.chartOption)
|
||||
|
||||
this.myChart.dispatchAction({
|
||||
type: 'takeGlobalCursor',
|
||||
key: 'brush',
|
||||
brushOption: {
|
||||
brushType: 'lineX',
|
||||
xAxisIndex: 'all',
|
||||
brushMode: 'single',
|
||||
throttleType: 'debounce'
|
||||
}
|
||||
})
|
||||
// 选中tab并刷新界面时,自动触发,避免markLine不显示的情况
|
||||
if (this.lineTab) {
|
||||
this.referenceSelectChange(this.lineRefer)
|
||||
}
|
||||
|
||||
this.myChart.on('brushEnd', this.brushEcharts)
|
||||
})
|
||||
}
|
||||
},
|
||||
activeChange (item, index, isClick) { // isClick:代表是通过点击操作来的
|
||||
if (this.isNoData) return
|
||||
if (isClick && this.lineTab === item.class) { // 点击高亮 tab 后取消高亮,恢复到全不高亮的状态
|
||||
this.legendSelectChange(item, index, 'active', true)
|
||||
this.lineTab = ''
|
||||
this.showMarkLine = false
|
||||
} else {
|
||||
this.lineTab = item.class
|
||||
this.legendSelectChange(item, index, 'active')
|
||||
this.showMarkLine = !item.invertTab
|
||||
}
|
||||
this.init(this.metric, this.showMarkLine, 'active')
|
||||
},
|
||||
mouseenter (item) {
|
||||
if (this.isNoData) return
|
||||
this.mousemoveCursor = item.class
|
||||
this.handleActiveBar(item.class)
|
||||
},
|
||||
mouseleave () {
|
||||
this.mousemoveCursor = ''
|
||||
},
|
||||
dispatchSelectAction (type, name) {
|
||||
this.myChart && this.myChart.dispatchAction({
|
||||
type: type,
|
||||
name: name
|
||||
})
|
||||
},
|
||||
legendSelectChange (item, index, val, isActiveAll) {
|
||||
if (index === 'index') {
|
||||
this.dispatchSelectAction('legendSelect', item.name)
|
||||
} else if (this.tabs[index] && this.tabs[index].name === item.name) {
|
||||
if (isActiveAll) {
|
||||
this.tabs.forEach((t) => {
|
||||
this.dispatchSelectAction('legendSelect', t.name)
|
||||
})
|
||||
} else {
|
||||
this.dispatchSelectAction('legendSelect', item.name)
|
||||
this.tabs.forEach((t) => {
|
||||
if (t.name !== item.name) {
|
||||
this.dispatchSelectAction('legendUnSelect', t.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (val === 'active') {
|
||||
this.tabs.forEach(t => {
|
||||
t.invertTab = item.name === t.name ? !t.invertTab : true
|
||||
if (t.invertTab && item.name === t.name) {
|
||||
this.lineTab = this.lineTab ? '' : t.class
|
||||
this.tabs.forEach((e) => {
|
||||
this.dispatchSelectAction('legendSelect', e.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
handleActiveBar () {
|
||||
if (document.querySelector('.network .line-value-tabs.is-active')) {
|
||||
const {
|
||||
offsetLeft,
|
||||
clientWidth,
|
||||
clientLeft
|
||||
} = document.querySelector('.network .line-value-tabs.is-active')
|
||||
const activeBar = document.querySelector('.network .line-value-active')
|
||||
activeBar.style.cssText += `width: ${clientWidth}px; left: ${offsetLeft + this.leftOffset + clientLeft}px;`
|
||||
}
|
||||
},
|
||||
resize () {
|
||||
if (this.myChart) {
|
||||
this.myChart.resize()
|
||||
}
|
||||
},
|
||||
referenceSelectChange (val) {
|
||||
this.lineRefer = val
|
||||
let echartsData
|
||||
if (this.lineTab) {
|
||||
echartsData = this.tabs.filter(t => t.show === true && t.class === this.lineTab) // t.invertTab === false
|
||||
} else {
|
||||
echartsData = this.tabs.filter(t => t.show === true)
|
||||
}
|
||||
if (!this.isUnitTesting) {
|
||||
const chartOption = this.myChart.getOption()
|
||||
|
||||
if (this.showMarkLine) {
|
||||
chartOption.series.forEach(t => {
|
||||
if (t.name === echartsData[0].name) {
|
||||
t.markLine.data = [{ yAxis: echartsData[0].analysis[getMarkLineByLineRefer(this.lineRefer)] }]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.myChart.setOption(chartOption)
|
||||
}
|
||||
},
|
||||
symbolSizeSortChange (index, time) {
|
||||
const dataIntegrationArray = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (stackedLineChartOption.series[i]) {
|
||||
const item = stackedLineChartOption.series[i].data.find(t => t[0] === time)
|
||||
if (item) {
|
||||
dataIntegrationArray.push(item)
|
||||
item[2] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataIntegrationArray.sort((a, b) => {
|
||||
return a[1] - b[1]
|
||||
})
|
||||
const sortIndex = dataIntegrationArray.findIndex(a => a[2] === index)
|
||||
return this.sizes[sortIndex]
|
||||
},
|
||||
initData (data, val, active, show, n) {
|
||||
let lineData = []
|
||||
const newData = [] // 接口数据的新构造排序数据
|
||||
if (data && data.length > 0) {
|
||||
data.forEach(item => {
|
||||
const obj = {}
|
||||
// 模板数据,按此进行排序
|
||||
const templateList = ['type', 'total', 'inbound', 'outbound', 'internal', 'through', 'other']
|
||||
for (const key in templateList) {
|
||||
const code = Object.keys(item).find(d => d.indexOf(templateList[key]) > -1)
|
||||
if (code) {
|
||||
obj[code] = _.clone(item[code])
|
||||
}
|
||||
}
|
||||
newData.push(obj)
|
||||
})
|
||||
}
|
||||
|
||||
if (data && data.length > 0) {
|
||||
newData.forEach((item) => {
|
||||
item.type = getLineType(item.type)
|
||||
if (item.type === val) {
|
||||
lineData = Object.keys(item).map(t => {
|
||||
return {
|
||||
...item[t]
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
lineData.splice(0, 1)
|
||||
// TODO 下面的逻辑是判断total曲线的尾部数据,从尾往前数0值的个数,若个数大于0,所有曲线都从尾部去掉相同数量的点,最多4个
|
||||
const totalData = lineData[0]
|
||||
if (_.get(totalData, 'values', []).length > 4) {
|
||||
let count = 0
|
||||
for (let i = totalData.values.length - 1; i >= totalData.values.length - 4; i--) {
|
||||
if (totalData.values[i].length > 1 && totalData.values[i][1] === 0) {
|
||||
count++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
lineData.forEach(l => {
|
||||
l.values.splice(l.values.length - count, count)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (val === metricType.Sessions) {
|
||||
const tabs = _.cloneDeep(this.tabsTemplate)
|
||||
lineData.forEach((d, i) => {
|
||||
tabs[i].data = d.values
|
||||
tabs[i].analysis = d.analysis
|
||||
})
|
||||
tabs.forEach((e, i) => {
|
||||
if (i !== 0) {
|
||||
e.show = false
|
||||
}
|
||||
const metric = metricOptions.find(item => item.value === metricType.Sessions)
|
||||
e.unitType = metric ? this.$t(metric.label) : ''
|
||||
e.invertTab = false
|
||||
this.lineTab = 'total'
|
||||
this.legendSelectChange(e, 0)
|
||||
})
|
||||
this.tabs = tabs
|
||||
this.$nextTick(() => {
|
||||
this.lineRefer = 'Average'
|
||||
this.echartsInit(this.tabs, true)
|
||||
})
|
||||
} else {
|
||||
let metric = metricOptions.find(item => item.value === val)
|
||||
if (!metric) {
|
||||
metric = metricOptions.find(item => item.value === metricType.Packets)
|
||||
}
|
||||
const unit = metric ? this.$t(metric.label) : ''
|
||||
this.legendInit(lineData, active, show, unit, n)
|
||||
}
|
||||
},
|
||||
legendInit (data, active, show, type, n) {
|
||||
const tabs = _.cloneDeep(this.tabsTemplate)
|
||||
data.forEach((d, i) => {
|
||||
tabs[i].data = d.values
|
||||
tabs[i].analysis = d.analysis
|
||||
})
|
||||
let num = 0
|
||||
const self = this
|
||||
tabs.forEach(e => {
|
||||
e.unitType = type
|
||||
if (e.name !== 'network.total' && parseFloat(e.analysis.max) === 0) {
|
||||
e.show = false
|
||||
num += 1
|
||||
} else {
|
||||
e.show = true
|
||||
if (!active && show !== self.lineRefer) {
|
||||
self.legendSelectChange(e, 'index')
|
||||
}
|
||||
}
|
||||
if (self.lineTab === e.class) {
|
||||
if (parseFloat(e.analysis.max) <= 0) {
|
||||
self.lineTab = ''
|
||||
self.lineRefer = ''
|
||||
// self.init() // 后续多测试关注
|
||||
}
|
||||
}
|
||||
})
|
||||
const emptyData = tabs.filter(d => parseFloat(d.analysis.max) === 0)
|
||||
this.isNoData = emptyData.length === tabs.length
|
||||
if (this.isNoData) {
|
||||
return true
|
||||
}
|
||||
this.tabs = tabs
|
||||
if (num === 5) {
|
||||
tabs[0].invertTab = false
|
||||
this.lineTab = 'total'
|
||||
this.legendSelectChange(tabs[0], 0)
|
||||
this.$nextTick(() => {
|
||||
this.echartsInit(this.tabs, true)
|
||||
})
|
||||
} else {
|
||||
if (n) this.lineTab = ''
|
||||
this.$nextTick(() => {
|
||||
this.echartsInit(this.tabs, show)
|
||||
if (!this.lineRefer) this.lineRefer = 'Average'
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* echarts框选
|
||||
* @param params
|
||||
*/
|
||||
brushEcharts (params) {
|
||||
this.myChart.dispatchAction({
|
||||
type: 'brush',
|
||||
areas: [] // 删除选框
|
||||
})
|
||||
if (!this.mouseDownFlag) {
|
||||
// 避免点击空白区域报错
|
||||
if (params.areas && params.areas.length > 0) {
|
||||
this.brushHistory.unshift({
|
||||
startTime: _.cloneDeep(this.timeFilter.startTime) * 1000,
|
||||
endTime: _.cloneDeep(this.timeFilter.endTime) * 1000
|
||||
})
|
||||
|
||||
const rangeObj = {
|
||||
startTime: Math.ceil(params.areas[0].coordRange[0]),
|
||||
endTime: Math.ceil(params.areas[0].coordRange[1])
|
||||
}
|
||||
|
||||
if (rangeObj.endTime - rangeObj.startTime < 5 * 60 * 1000) {
|
||||
rangeObj.startTime = rangeObj.endTime - 5 * 60 * 1000
|
||||
}
|
||||
this.$store.commit('setRangeEchartsData', rangeObj)
|
||||
}
|
||||
}
|
||||
},
|
||||
httpError (e) {
|
||||
this.isNoData = false
|
||||
this.showError = true
|
||||
this.errorMsg = this.errorMsgHandler(e)
|
||||
},
|
||||
metricChange (value) {
|
||||
const { query } = this.$route
|
||||
const newUrl = urlParamsHandler(window.location.href, query, {
|
||||
metric: value
|
||||
})
|
||||
overwriteUrl(newUrl)
|
||||
},
|
||||
reload (startTime, endTime, dateRangeValue) {
|
||||
this.timeFilter = { startTime: getSecond(startTime), endTime: getSecond(endTime) }
|
||||
const { query } = this.$route
|
||||
// 因为详情页是打开的新标签页,设置时间并不会影响到其他界面,所以store的名称不必改变
|
||||
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)
|
||||
},
|
||||
timeRefreshChange () {
|
||||
// 不是自选时间
|
||||
if (this.$refs.dateTimeRange) {
|
||||
if (!this.$refs.dateTimeRange.isCustom) {
|
||||
const value = this.timeFilter.dateRangeValue
|
||||
this.$refs.dateTimeRange.quickChange(value)
|
||||
} else {
|
||||
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
|
||||
}
|
||||
} else {
|
||||
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.myChart = null
|
||||
this.chartOption = null
|
||||
const self = this
|
||||
self.timer = setTimeout(() => {
|
||||
if (self.lineTab && self.metric !== metricType.Sessions) {
|
||||
const data = self.tabsTemplate.find(t => t.class === self.lineTab)
|
||||
self.activeChange(data, data.positioning)
|
||||
} else {
|
||||
self.init()
|
||||
}
|
||||
}, 200)
|
||||
window.addEventListener('resize', this.resize)
|
||||
},
|
||||
beforeUnmount () {
|
||||
clearTimeout(this.timer)
|
||||
window.removeEventListener('resize', this.resize)
|
||||
|
||||
let myChart = echarts.getInstanceByDom(this.$refs.overviewLineChart)
|
||||
if (myChart) {
|
||||
echarts.dispose(myChart)
|
||||
}
|
||||
if (this.myChart) {
|
||||
echarts.dispose(this.myChart)
|
||||
}
|
||||
// 检测时发现该方法占用较大内存,且未被释放
|
||||
this.unitConvert = null
|
||||
this.valueToRangeValue = null
|
||||
myChart = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -325,6 +325,73 @@ export const stackedLineChartOption = {
|
||||
}
|
||||
]
|
||||
}
|
||||
// subscriber的app页签
|
||||
export const stackedLineWithLegendChartOption = {
|
||||
color: chartColor3,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
className: 'echarts-tooltip echarts-tooltip-dark'
|
||||
},
|
||||
toolbox: {
|
||||
show: false
|
||||
},
|
||||
brush: {
|
||||
toolbox: ['lineX'],
|
||||
xAxisIndex: 'all',
|
||||
throttleType: 'debounce',
|
||||
transformable: false
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
left: 'center',
|
||||
bottom: 10,
|
||||
icon: 'roundRect',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10
|
||||
},
|
||||
grid: {
|
||||
top: '12%',
|
||||
left: '2%',
|
||||
right: '1.5%',
|
||||
bottom: 40,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'time',
|
||||
splitNumber: 8,
|
||||
axisLabel: {
|
||||
formatter: xAxisTimeFormatter,
|
||||
rich: xAxisTimeRich
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
return unitConvert(value, unitTypes.number).join('')
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
symbol: 'circle',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const linkTrafficLineChartOption = {
|
||||
color: chartColor3,
|
||||
@@ -454,7 +521,6 @@ export const npmLineChartOption = {
|
||||
xAxis: [
|
||||
{
|
||||
type: 'time',
|
||||
splitNumber: 8,
|
||||
axisLabel: {
|
||||
formatter: xAxisTimeFormatter,
|
||||
rich: xAxisTimeRich
|
||||
|
||||
Reference in New Issue
Block a user